Asked  7 Months ago    Answers:  5   Viewed   316 times

I'm trying to create a JFX11 self-containing jar using maven dependencies. From the research I've done, it seems the best way to do this is through the maven shade plugin. However, When I run it, I get the this error:

Error: JavaFX runtime components are missing, and are required to run this application

I don't understand why this is happening. What am I messing up? Is there a better way to do this? I've also tried the maven assembly plugin with the same message.

pom file for reference

<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>Application</groupId>
    <artifactId>Main</artifactId>
    <packaging>jar</packaging>
    <version>1.0-SNAPSHOT</version>
    <name>SpaceRunner</name>
    <url>http://maven.apache.org</url>
    <dependencies>
        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-controls</artifactId>
            <version>11</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.0</version>
                <configuration>
                    <release>10</release>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>1.6.0</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>java</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <mainClass>Application.Main</mainClass>
                </configuration>
            </plugin>
            <plugin>
                <artifactId>maven-jar-plugin</artifactId>
                <configuration>
                    <archive>
                        <manifest>
                            <mainClass>
                                Application.Main
                            </mainClass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>3.2.0</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <transformers>
                                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                    <mainClass>Application.Main</mainClass>
                                </transformer>
                            </transformers>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

 Answers

98

This answer explains why a fat/uber jar fails on JavaFX 11. In short:

This error comes from sun.launcher.LauncherHelper in the java.base module. The reason for this is that the Main app extends Application and has a main method. If that is the case, the LauncherHelper will check for the javafx.graphics module to be present as a named module. If that module is not present, the launch is aborted.

And already proposes a fix for Gradle.

For Maven the solution is exactly the same: provide a new main class that doesn't extend from Application.

You will have new class in your application package (bad name):

// NewMain.java
public class NewMain {

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

And your existing Main class, as is:

//Main.java
public class Main extends Application {

    @Override
    public void start(Stage stage) {
        ...
    }

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

Now you need to modify your pom and set your main class for the different plugins:

<mainClass>application.NewMain</mainClass>

Platform-specific Fat jar

Finally, with the shade plugin you are going to produce a fat jar, on your machine.

This means that, so far, your JavaFX dependencies are using a unique classifier. If for instance you are on Windows, Maven will be using internally the win classifier. This has the effect of including only the native libraries for Windows.

So you are using:

  • org.openjfx:javafx-controls:11
  • org.openjfx:javafx-controls:11:win
  • org.openjfx:javafx-graphics:11
  • org.openjfx:javafx-graphics:11:win <-- this contains the native dlls for Windows
  • org.openjfx:javafx-base:11
  • org.openjfx:javafx-base:11:win

Now, if you produce the fat jar, you will bundle all those dependencies (and those other regular third party dependencies from your project), and you will be able to run your project as:

java -jar myFatJar-1.0-SNAPSHOT.jar

While this is very nice, if you want to distribute you jar, be aware that this jar is not cross-platform, and it will work only on your platform, in this case Windows.

Cross-Platform Fat Jar

There is a solution to generate a cross-platform jar that you can distribute: include the rest of the native libraries of the other platforms.

This can be easily done, as you just need to include the graphics module dependencies for the three platforms:

<dependencies>
    <dependency>
        <groupId>org.openjfx</groupId>
        <artifactId>javafx-controls</artifactId>
        <version>11</version>
    </dependency>
    <dependency>
        <groupId>org.openjfx</groupId>
        <artifactId>javafx-graphics </artifactId>
        <version>11</version>
        <classifier>win</classifier>
    </dependency>
    <dependency>
        <groupId>org.openjfx</groupId>
        <artifactId>javafx-graphics </artifactId>
        <version>11</version>
        <classifier>linux</classifier>
    </dependency>
    <dependency>
        <groupId>org.openjfx</groupId>
        <artifactId>javafx-graphics </artifactId>
        <version>11</version>
        <classifier>mac</classifier>
    </dependency>
</dependencies>

Size

There is a main issue with this approach: the size. As you can see in this other answer, if you use the WebView control, you will be bundling around 220 MB due to the WebKit native libraries.

Tuesday, June 1, 2021
 
Camsoft
answered 7 Months ago
70

There are similar questions like this or this other one.

Before JavaFX 11, whenever you were calling something JavaFX related, you had all the javafx modules available within the SDK.

But now you have to include the modules/dependencies you need.

Your error says that you are using FXML but it can't be resolved, but you have just added the javafx.controls module:

--add-modules=javafx.controls

As you can see in the JavaDoc the javafx.controls module depends on javafx.graphics and java.base, but none of those modules includes the FXML classes.

If you need FXML classes like the FXMLLoader, you need to include javafx.fxml module:

 --module-path="C:Program FilesJavajavafx-sdk-11lib" 
    --add-modules=javafx.controls,javafx.fxml

The same will apply if you need media or webkit, those have their own modules.

Wednesday, June 2, 2021
 
Manju
answered 6 Months ago
43

These are a few options for packaging/distributing a (non-modular) JavaFX 11 end application. Most of them are explained in the official OpenJFX docs.

I'll use this sample as reference. I'll also use Gradle. Similar can be done with Maven (different plugins), and even without build tools (but this is not recommended...). Build tools are a must nowadays.

Fat Jar

This is still a valid option, but not the preferred one, as it breaks the modular design and bundles everything all together, and it is not cross-platform unless you take care of that.

For the given sample, you have a build.gradle file like this:

plugins {
    id 'application'
    id 'org.openjfx.javafxplugin' version '0.0.5'
}

repositories {
    mavenCentral()
}

dependencies {
}

javafx {
    modules = [ 'javafx.controls' ]
}

mainClassName = 'hellofx.HelloFX'

jar {
    manifest {
        attributes 'Main-Class': 'hellofx.Launcher'
    }
    from {
        configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
    }
}

Note the use of a Launcher class. As mentioned by the OP or explained here, a launcher class that not extends from Application is required now to create a fat jar.

Running ./gradlew jar produces a fat jar (~8 MB) that includes the JavaFX classes, and the native libraries of your current platform.

You can run java -jar build/libs/hellofx.jar as usual, but only in the same platform.

As explained in the OpenJFX docs or here, you can still create a cross-platform jar.

In this case, we can include the three graphics jars, as those are the ones that have platform-dependent code and libraries. Base, controls and fxml modules are the platform-independent.

dependencies {
    compile "org.openjfx:javafx-graphics:11.0.1:win"
    compile "org.openjfx:javafx-graphics:11.0.1:linux"
    compile "org.openjfx:javafx-graphics:11.0.1:mac"
}

./gradlew jar will produce a fat jar (19 MB) now that can be distributed to these three platforms.

(Note Media and Web have also platform-dependent code/native libraries).

So this works as it used to on Java 8. But as I said before, it breaks how modules work, and it doesn't align on how libraries and apps are distributed nowadays.

And don't forget that the users of these jars will still have to install a JRE.

jlink

So what about distributing a custom image with your project, that already includes a native JRE and a launcher?

You will say that if you have a non-modular project, that won't work. True. But let's examine two options here, before talking about jpackage.

runtime-plugin

The badass-runtime-plugin is a Gradle plugin that creates runtime images from non-modular projects.

With this build.gradle:

plugins {
    id 'org.openjfx.javafxplugin' version '0.0.5'
    id 'org.beryx.runtime' version '1.0.0'
    id "com.github.johnrengelman.shadow" version "4.0.3"
}

repositories {
    mavenCentral()
}

dependencies {
}

javafx {
    modules = [ 'javafx.controls' ]
}

mainClassName = 'hellofx.Launcher'

runtime {
    options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages']
}

When you run ./gradlew runtime it will create a runtime, with its launcher, so you can run:

cd build/image/hellofx/bin
./hellofx

Note it relies on the shadow plugin, and it requires a Launcher class as well.

If you run ./gradlew runtimeZip, you can get a zip for this custom image of about 32.5 MB.

Again, you can distribute this zip to any user with the same platform, but now there is no need of an installed JRE.

See targetPlatform for building images for other platforms.

Going Modular

We keep thinking that we have non-modular project, and that can't be changed... but what if we do change it?

Going modular is not that big of a change: you add a module-info.java descriptor, and you include the required modules on it, even if those are non-modular jars (based on automatic names).

Based on the same sample, I'll add a descriptor:

module hellofx {
    requires javafx.controls;

    exports hellofx;
}

And now I can use jlink on command line, or use a plugin for it. The badass-gradle-plugin is a gradle plugin, from the same author as the one mentioned before, that allows creating a custom runtime.

With this build file:

plugins {
    id 'org.openjfx.javafxplugin' version '0.0.5'
    id 'org.beryx.jlink' version '2.3.0'
}

repositories {
    mavenCentral()
}

dependencies {
}

javafx {
    modules = [ 'javafx.controls' ]
}

mainClassName = 'hellofx/hellofx.HelloFX'

you can run now:

./gradlew jlink
cd build/image/bin/hellofx
./hellofx

or ./gradlew jlinkZip for a zipped version (31 MB) that can be distributed and run in machines, of the same platform, even if there is no JRE installed.

As you can see, no need for shadow plugin or Launcher class. You can also target other platforms, or include non-modular dependencies, like in this question.

jpackage

Finally, there is a new tool to create executable installers that you can use to distribute your application.

So far there is no GA version yet (probably we'll have to wait for Java 13), but there are two options to use it now with Java 11 or 12:

With Java/JavaFX 11 there is a back port from the initial work on the JPackager on Java 12 that you can find here. There is a nice article about using it here, and a gradle project to use it here.

With Java/JavaFX 12 there is already a build 0 version of the jpackage tool that will be available with Java 13.

This is a very preliminary use of the tool:

plugins {
    id 'org.openjfx.javafxplugin' version '0.0.5'
}

repositories {
    mavenCentral()
}

dependencies {
}

javafx {
    version = "12-ea+5"
    modules = [ 'javafx.controls' ]
}

mainClassName = 'hellofx/hellofx.HelloFX'

def java_home = '/Users/<user>/Downloads/jdk-12.jdk/Contents/Home'
def installer = 'build/installer'
def appName = 'HelloFXApp'

task copyDependencies(type: Copy) {
    dependsOn 'build'
    from configurations.runtime
    into "${buildDir}/libs"
}

task jpackage(type: Exec) {
    dependsOn 'clean'
    dependsOn 'copyDependencies'

    commandLine "${java_home}/bin/jpackage", 'create-installer', "dmg",
            '--output', "${installer}", "--name", "${appName}",
            '--verbose', '--echo-mode', '--module-path', 'build/libs',
            '--add-modules', "${moduleName}", '--input', 'builds/libraries',
            '--class', "${mainClassName}", '--module', "${mainClassName}"
}

Now running ./gradlew jpackage generates a dmg (65 MB), that I can distribute to be installed:

installer

Conclusion

While you can stick to classic fat jars, when moving to Java 11 and beyond, everything is supposed to be modular. The new (soon to be) available tools and plugins, including the IDE support, are helping during this transition.

I know I've presented here the simplest use case, and that when trying more complex real cases, there will be several issues... But we should better work on solving those issues rather than keep using outdated solutions.

Wednesday, June 2, 2021
 
Skipper
answered 6 Months ago
86

Your problem is not compiling the project, but running it. Since your main is defined in your Application-extension, running the project will require JavaFX in your module path on startup.

So either outsource your main into a class different from your Application or add the JavaFX modules with VM arguments:

--module-path="<javafx-root>lib" --add-modules="javafx.base,javafx.controls,..."

See this for some more info.

Thursday, August 5, 2021
 
QuantumMechanic
answered 4 Months ago
74

There is the javafx-maven-plugin which is available for maven.

When developing with Java 8 you just put that plugin as some build-plugin, without further dependencies.

<plugin>
    <groupId>com.zenjava</groupId>
    <artifactId>javafx-maven-plugin</artifactId>
    <version>8.8.3</version>
    <configuration>
        <mainClass>your.main.class.which.extends.javafx.Application</mainClass>
    </configuration>
</plugin>

Calling mvn jfx:jar creates your javafx-application-jar inside target/jfx/app/yourapp-jfx.jar, or even creates native launcher (like EXE-file) when calling mvn jfx:native.

Disclaimer: I'm the maintainer of the javafx-maven-plugin.

Thursday, August 12, 2021
 
Eddas
answered 4 Months ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :  
Share