1

I have a very simple Java class, src/main/java/webbrowser/WebBrowser.java:

package webbrowser;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.web.WebView;
import javafx.scene.web.WebEngine;
import javafx.stage.Stage;

public class WebBrowser extends Application {

    @Override
    public void start(Stage stage) {
        WebView browser = new WebView();
        WebEngine webEngine = browser.getEngine();
        webEngine.load("http://www.oracle.com");
        Scene scene = new Scene(browser, 1200, 900);
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch();
    }

}

I have a pom.xml file that lists the JavaFX dependencies and configures the assembly plugin:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>webbrowser</groupId>
    <artifactId>WebBrowser</artifactId>
    <packaging>jar</packaging>
    <version>1.0-SNAPSHOT</version>
    <name>WebBrowser</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-web</artifactId>
            <version>18-ea+9</version>         
        </dependency>
        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-controls</artifactId>
            <version>18-ea+9</version>
        </dependency>
    </dependencies>
  
    <build>
        <finalName>WebBrowser</finalName>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-assembly-plugin</artifactId>
                    <executions>
                        <execution>
                            <phase>package</phase>
                            <goals>
                                <goal>single</goal>
                            </goals>                            
                        </execution>
                    </executions>
                    <configuration>
                        <archive>
                            <manifest>
                                <mainClass>
                                    webbrowser.WebBrowser
                                </mainClass>
                            </manifest>
                        </archive>
                        <descriptorRefs>
                            <descriptorRef>jar-with-dependencies</descriptorRef>
                        </descriptorRefs>
                    </configuration>
                </plugin>       
            </plugins>
        </pluginManagement>
    </build>  
</project>

When I run on the command-line, mvn clean install assembly:single, it says "BUILD SUCCESS" and there are no warnings or errors in the log.

Then I run:

% java -jar target/WebBrowser-jar-with-dependencies.jar
Error: JavaFX runtime components are missing, and are required to run this application

For some reason, the mvn assembly:single plugin did not include every JavaFX dependency.

However, when I run this on the command-line, it works fine and launches the application:

% java --module-path $JAVAFX --add-modules javafx.controls,javafx.web -jar target/WebBrowser-jar-with-dependencies.jar

Questions

  1. Why does the assembly:single goal not package the javafx.controls and javafx.web dependencies inside target/WebBrowser-jar-with-dependencies.jar?
  2. How can I configure the Assembly plugin to include the javafx.controls and javafx.web dependencies inside an all-in-one, runnable target/WebBrowser-jar-with-dependencies.jar file?
ktm5124
  • 11,861
  • 21
  • 74
  • 119
  • Have you tried with a javafx maven plugin which may have solved this issues already? https://github.com/openjfx/javafx-maven-plugin – emeraldjava Jan 21 '22 at 19:16
  • 1
    @emeraldjava I tried using the javafx-maven plugin but I don't see a way to build an all-in-one runnable jar file using javafx-maven plugin – ktm5124 Jan 21 '22 at 19:23
  • 4
    The JavaFX runtime contains more than just class files and resources; it can't reliably be encapsulated in a jar file. The recommended approach is to create a native bundle. Create a custom Java runtime which includes JavaFX (using `jlink`, or a maven plugin for it) and then create a native application with the jar and runtime, e.g. using `jpackage`. There are dozens of questions referring to this on this forum. – James_D Jan 21 '22 at 19:59

2 Answers2

5

Recommended: Change your packaging approach

Don't try to create a single jar for your application.

There are links to packaging resources under the:

I suggest you review the information there, then choose and implement an appropriate (and different) packaging solution for your application.

Understanding the error message: JavaFX runtime components are missing

It means the JavaFX runtime is not accessible to your application. JavaFX modules should be on your module path, which is currently not possible if you put them in your application jar.

See the Eden coding tutorial for some information on this and some basic solutions (though it does not cover packaging apps in general as that is a very complex topic):

To address your specific questions

1 Why javafx dependencies aren't in the jar-with-dependencies.jar

I copied your code and tried it locally on a Windows machine with OpenJDK 17.

A jar-with-dependencies was built and the javafx Windows libraries were included in the jar. You can see this by running jar tvf on the jar file. It includes both the JavaFX platform class files and the Windows native DLLs.

But when the jar was built, there was an info message:

[INFO] module-info.class already added, skipping

This is key. Each modular JavaFX jar has its own module-info file which is required for it to function in a modular environment. When some are skipped, then the modularity is broken, and the app will no longer run off the module path.

It would be nice if the Maven assembly plugin was clever enough to recognize modular jars are in the dependencies and explicitly warn about that when asked to create a single jar-with-dependencies, because the java runtime does not currently support multi-module jars.

To understand more about why you can't currently put multiple Java platform modules in a single jar, see:

2 Creating a jar-with-dependencies

You could switch from the assembly plugin to the shade plugin and use the solution at:

But you don't have to, you can continue using the assembly plugin for this if that is what you really want to do.

If you do so, understand the warning in the link that then you are running JavaFX in an unsupported configuration off the classpath rather than the module path.

To do this with an assembly:

  1. Add a launcher class to trick the JavaFX runtime into allowing itself to run from the classpath instead of the module path.

    package webbrowser;
    
    public class WebBrowserLauncher {
        public static void main(String[] args) {
            WebBrowser.main(args);
        }
    }
    
  2. Change the main class defined for your assembly to reference the new class:

    <mainClass>
        webbrowser.WebBrowserLauncher
    </mainClass>
    
  3. If you want to allow the app to run on different operating systems than the one you are building it on, then add dependencies for those platforms to your pom.xml. For an example of the additional dependencies required, see:

I made changes 1&2 and tested them and your app packaged this way ran for me. I didn't bother with change 3 as I can't easily test that at this time.

Execution command and output:

java -jar target/WebBrowser-jar-with-dependencies.jar
Jan 21, 2022 7:59:51 PM com.sun.javafx.application.PlatformImpl startup
WARNING: Unsupported JavaFX configuration: classes were loaded from 'unnamed module @41dc7fe'

The warning exists because JavaFX is being loaded from the classpath rather than the module path.

Assembling multiple jars into a zip or tar archive

Assembly could still be a useful plugin to use in your build process, but for a different purpose.

If you did want to stick with an assembly approach, you could:

  1. Create a jar without dependencies.
  2. Use the assembly to create a lib directory containing your dependencies and the JavaFX modules for the platforms you need to support.
  3. Provide a platform-specific script to launch the app, which appropriately configures the classpath and the module path to use the library dependencies extracted from the archive.
    • This is a convenience as the user could type the full command into the command line, though that would probably be more painful than just executing a script.
  4. You can then create a custom assembly that includes all of those things in a tar or zip file.

The user would then be able to decompress your archive and use your launcher script to run the app (as long as they had an appropriate JVM preinstalled).

Assembly would be good for that, but jlink or jpackage may be preferred alternatives regardless as those options don't require a preinstalled JVM.

The openjfx-maven-plugin has a similar option in its jlink feature but includes a linked java runtime:

  • jlinkZipName: When set, creates a zip of the resulting runtime image.
jewelsea
  • 150,031
  • 14
  • 366
  • 406
2

Beside jewelsea's perfect answer, here is a minimal gradle approach that just works... (for the OS you use, with an executable webbrowser-all.jar)

build.gradle

plugins {
    id 'application'
    id 'org.openjfx.javafxplugin'  version '0.0.11'
    id 'com.github.johnrengelman.shadow' version '7.1.2'
}

repositories {
    mavenCentral()
}

application {
    mainClass = 'webbrowser.Launch'
}

javafx {
    modules = [ 'javafx.controls', 'javafx.web']
    version = '18+'
}

shadowJar {
    baseName = 'webbrowser'
    classifier = 'all'
}

Add a launcher Launch.java

package webbrowser;

import javafx.application.Application;

public class Launch {
public static void main(String[] args) {
    Application.launch(WebBrowser.class, args);
    }
}

And your WebBrowser.java

package webbrowser;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.Stage;

public class WebBrowser extends Application{

    @Override
    public void start(Stage stage) {
        WebView browser = new WebView();
        WebEngine webEngine = browser.getEngine();
        webEngine.load("http://www.oracle.com");
        Scene scene = new Scene(browser, 1200, 900);
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch();
    }

}