JavaFX 11 : Create a jar file with Gradle
Asked Answered
D

3

45

I am trying to upgrade a JavaFX project from the 8 Java version to the 11 version. It works when I use the "run" Gradle task (I followed the Openjfx tutorial), but when I build (with the "jar" Gradle task) and execute (with "java -jar") a jar file, the message "Error: JavaFX runtime components are missing, and are required to run this application" appears.

Here is my build.gradle file :

group 'Project'
version '1.0'
apply plugin: 'java'
sourceCompatibility = 1.11

repositories {
    mavenCentral()
}

def currentOS = org.gradle.internal.os.OperatingSystem.current()
def platform
if (currentOS.isWindows()) {
    platform = 'win'
} else if (currentOS.isLinux()) {
    platform = 'linux'
} else if (currentOS.isMacOsX()) {
    platform = 'mac'
}
dependencies {
    compile "org.openjfx:javafx-base:11:${platform}"
    compile "org.openjfx:javafx-graphics:11:${platform}"
    compile "org.openjfx:javafx-controls:11:${platform}"
    compile "org.openjfx:javafx-fxml:11:${platform}"
}

task run(type: JavaExec) {
    classpath sourceSets.main.runtimeClasspath
    main = "project.Main"
}

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

compileJava {
    doFirst {
        options.compilerArgs = [
                '--module-path', classpath.asPath,
                '--add-modules', 'javafx.controls,javafx.fxml'
        ]
    }
}

run {
    doFirst {
        jvmArgs = [
                '--module-path', classpath.asPath,
                '--add-modules', 'javafx.controls,javafx.fxml'
        ]
    }
}

Do you know what I should do ?

Digestant answered 29/9, 2018 at 15:15 Comment(0)
D
6

[Edit: For the latest versions of JavaFX, please check my second answer]

If someone is interested, I found a way to create jar files for a JavaFX11 project (with Java 9 modules). I tested it on Windows only (if the application is also for Linux, I think we have to do the same steps but on Linux to get JavaFX jars for Linux).

I have a "Project.main" module (created by IDEA, when I created a Gradle project) :

 src
 +-- main
 |   +-- java
     |   +-- main
         |   +-- Main.java (from the "main" package, extends Application)
     |   +-- module-info.java
 build.gradle
 settings.gradle
 ...

The module-info.java file :

module Project.main {
    requires javafx.controls;
    exports main;
}

The build.gradle file :

plugins {
    id 'java'
}

group 'Project'
version '1.0'
ext.moduleName = 'Project.main'
sourceCompatibility = 1.11

repositories {
    mavenCentral()
}

def currentOS = org.gradle.internal.os.OperatingSystem.current()
def platform
if (currentOS.isWindows()) {
    platform = 'win'
} else if (currentOS.isLinux()) {
    platform = 'linux'
} else if (currentOS.isMacOsX()) {
    platform = 'mac'
}
dependencies {
    compile "org.openjfx:javafx-base:11:${platform}"
    compile "org.openjfx:javafx-graphics:11:${platform}"
    compile "org.openjfx:javafx-controls:11:${platform}"
}

task run(type: JavaExec) {
    classpath sourceSets.main.runtimeClasspath
    main = "main.Main"
}

jar {
    inputs.property("moduleName", moduleName)
    manifest {
        attributes('Automatic-Module-Name': moduleName)
    }
}

compileJava {
    inputs.property("moduleName", moduleName)
    doFirst {
        options.compilerArgs = [
                '--module-path', classpath.asPath,
                '--add-modules', 'javafx.controls'
        ]
        classpath = files()
    }
}

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

The settings.gradle file :

rootProject.name = 'Project'

And the Gradle commands :

#Run the main class
gradle run

#Create the jars files (including the JavaFX jars) in the "build/libs" folder
gradle createJar

#Run the jar file
cd build/libs
java --module-path "." --module "Project.main/main.Main"
Digestant answered 2/10, 2018 at 20:18 Comment(9)
I only defined the "build.gradle" file. Then, I just used the gradle command (you can use it if you installed Gradle) If I remember well, the configuration is easier with the last JavaFX versions, thanks to the JavaFX plugin in Gradle (openjfx.io/openjfx-docs/#gradle). So all these steps are probably useless now.Digestant
We can especially use jlink to get an application file (section "Runtime images")Digestant
I don't understand. After applying the plugin, setting the build.gradle I just have to run jLink task and what should I distribute to my clients? The "build" folder? I'm confusedLavinialavinie
With the method above (not jlink), the jar can be found in build/libs. With jlink, I suppose that you can find the generated distributable file (a zip file containing a .bat and a .sh file to run the app, if I remember well) in a subfolder of build.Digestant
Yeah. I don't need to have JRE at my client's pc?Lavinialavinie
You need to have one if you create a jar file (method without jlink). With jlink, you don't need one (it includes a "light" JRE containing only the Java core modules that you need)Digestant
Thanks, Flpe. I'm using Jlink but having some troubles to distribuite it to WindowsLavinialavinie
I made an example using Jlink. If you are interested, please check the new answer I will add to this questionDigestant
@Digestant I'm not having any success with getting this working. Without adding any Groovy complications, I find gradle run doesn't work and gradle createJar runs but the java command just hangs. Error from gradle run is "Error: JavaFX runtime components are missing, and are required to run this application". I get this both in W10 and Linux, and I have tried both Eclipse and Intellij.Willdon
L
51

With Java/JavaFX 11, the shadow/fat jar won't work.

As you can read here:

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:

Optional<Module> om = ModuleLayer.boot().findModule(JAVAFX_GRAPHICS_MODULE_NAME);

If that module is not present, the launch is aborted. Hence, having the JavaFX libraries as jars on the classpath is not allowed in this case.

What's more, every JavaFX 11 jar has a module-info.class file, at the root level.

When you bundle all the jars content into one single fat jar, what happens to those files with same name and same location? Even if the fat jar keeps all of them, how does that identify as a single module?

There is a request to support this, but it hasn't been addressed yet: http://openjdk.java.net/projects/jigsaw/spec/issues/#MultiModuleExecutableJARs

Provide a means to create an executable modular “uber-JAR” that contains more than one module, preserving module identities and boundaries, so that an entire application can be shipped as a single artifact.

The shadow plugin still does make sense to bundle all your other dependencies into one jar, but after all you will have to run something like:

java --module-path <path-to>/javafx-sdk-11/lib \
   --add modules=javafx.controls -jar my-project-ALL-1.0-SNAPSHOT.jar

This means that, after all, you will have to install the JavaFX SDK (per platform) to run that jar which was using JavaFX dependencies from maven central.

As an alternative you can try to use jlink to create a lightweight JRE, but your app needs to be modular.

Also you could use the Javapackager to generate an installer for each platform. See http://openjdk.java.net/jeps/343 that will produce a packager for Java 12.

Finally, there is an experimental version of the Javapackager that works with Java 11/JavaFX 11: http://mail.openjdk.java.net/pipermail/openjfx-dev/2018-September/022500.html

EDIT

Since the Java launcher checks if the main class extends javafx.application.Application, and in that case it requires the JavaFX runtime available as modules (not as jars), a possible workaround to make it work, should be adding a new Main class that will be the main class of your project, and that class will be the one that calls your JavaFX Application class.

If you have a javafx11 package with the Application class:

public class HelloFX extends Application {

    @Override
    public void start(Stage stage) {
        String javaVersion = System.getProperty("java.version");
        String javafxVersion = System.getProperty("javafx.version");   
        Label l = new Label("Hello, JavaFX " + javafxVersion + ", running on Java " + javaVersion + ".");
        Scene scene = new Scene(new StackPane(l), 400, 300);
        stage.setScene(scene);
        stage.show();
    }

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

}

Then you have to add this class to that package:

public class Main {

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

And in your build file:

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

Now you can run:

./gradlew run

or

./gradlew jar
java -jar build/libs/javafx11-1.0-SNAPSHOT.jar

JavaFX from FAT jar

The final goal is to have the JavaFX modules as named modules on the module path, and this looks like a quick/ugly workaround to test your application. For distribution I'd still suggest the above mentioned solutions.

Lyris answered 29/9, 2018 at 19:18 Comment(5)
Did you check with the code posted on the answer? It should work for you, and then you should update your code in the same way. Create a new question otherwise.Powerful
Aha, my mistake. In the Main::main method I called HelloFX.launch(args), which is not allowed, instead of HelloFX.main(args). Calling HelloFX.launch(HelloFX.class, args) is fine though so the HelloFX::main method is redundant in this case (e.g., the IDEs can use it).Deductible
@JoséPereda, that still is the answer? There are no updates?Lavinialavinie
I've tried with just your edit and got exceptions: Exception in thread "main" java.lang.NoClassDefFoundError: javafx/application/ApplicationLavinialavinie
@Lavinialavinie this answer is updated to use the JavaFX gradle plugin.Powerful
D
8

With the latest versions of JavaFX, you can use two Gradle plugins to easily distribute your project (javafxplugin and jlink).

With these plugins, you can:

  • Create a distributable zip file that contains all the needed jar files: it requires a JRE to be executed (with the bash or batch script)
  • Create a native application with Jlink for a given OS: a JRE is not needed to execute it, as Jlink includes a "light" JRE (including only the needed java modules and dependencies) in the distribution folder

I made an example on bitbucket, if you want an example.

Digestant answered 24/10, 2019 at 12:38 Comment(7)
Great. Anyone interested, you can also using the Gradle installdist task, which will produce the /bin and /lib directories, so you don't have to unpack the tar/zip as with assemble. The jlink command also produced some output, but no directory "image" under "build". I know nothing whatever about what's going on with that, but the first option is fine by me.Willdon
I have tried to use this with Java 11 (my OS version currently)... This works fine at the CLI, but Eclipse complains that it can't find the FX classes any more. Any idea why that might be?Willdon
Did you import the project as a Gradle project ? It will probably not work if you open it as a normal project. I used Intellij IDEA to create this sample application (so it should work at least with IDEA)Digestant
Thanks... I started it up as a Gradle project, yes. I've narrowed down the problem after lots of fiddling around: please see this question I've just asked: https://mcmap.net/q/276961/-how-can-i-stop-gradle-deleting-jars-from-my-project-39-s-module-path/595305.Willdon
If you have a moment, could you take a look at this question I've just asked: https://mcmap.net/q/276962/-use-groovy-app-and-test-code-in-combination-with-jlink-solution-for-bundling-javafx/595305 ? It's about switching to Groovy app & test code whilst using your solution, and the errors I've encountered trying to do so. You might have some insight...Willdon
Awesome boilerplate, thank you. You should ask jlink guys to add it to examples section.Scientific
Thank you for the compliment, nice to see that it is helpful. I suppose I have to improve a bit the README file and comments before asking to add it to an example section. But thanks for the idea.Digestant
D
6

[Edit: For the latest versions of JavaFX, please check my second answer]

If someone is interested, I found a way to create jar files for a JavaFX11 project (with Java 9 modules). I tested it on Windows only (if the application is also for Linux, I think we have to do the same steps but on Linux to get JavaFX jars for Linux).

I have a "Project.main" module (created by IDEA, when I created a Gradle project) :

 src
 +-- main
 |   +-- java
     |   +-- main
         |   +-- Main.java (from the "main" package, extends Application)
     |   +-- module-info.java
 build.gradle
 settings.gradle
 ...

The module-info.java file :

module Project.main {
    requires javafx.controls;
    exports main;
}

The build.gradle file :

plugins {
    id 'java'
}

group 'Project'
version '1.0'
ext.moduleName = 'Project.main'
sourceCompatibility = 1.11

repositories {
    mavenCentral()
}

def currentOS = org.gradle.internal.os.OperatingSystem.current()
def platform
if (currentOS.isWindows()) {
    platform = 'win'
} else if (currentOS.isLinux()) {
    platform = 'linux'
} else if (currentOS.isMacOsX()) {
    platform = 'mac'
}
dependencies {
    compile "org.openjfx:javafx-base:11:${platform}"
    compile "org.openjfx:javafx-graphics:11:${platform}"
    compile "org.openjfx:javafx-controls:11:${platform}"
}

task run(type: JavaExec) {
    classpath sourceSets.main.runtimeClasspath
    main = "main.Main"
}

jar {
    inputs.property("moduleName", moduleName)
    manifest {
        attributes('Automatic-Module-Name': moduleName)
    }
}

compileJava {
    inputs.property("moduleName", moduleName)
    doFirst {
        options.compilerArgs = [
                '--module-path', classpath.asPath,
                '--add-modules', 'javafx.controls'
        ]
        classpath = files()
    }
}

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

The settings.gradle file :

rootProject.name = 'Project'

And the Gradle commands :

#Run the main class
gradle run

#Create the jars files (including the JavaFX jars) in the "build/libs" folder
gradle createJar

#Run the jar file
cd build/libs
java --module-path "." --module "Project.main/main.Main"
Digestant answered 2/10, 2018 at 20:18 Comment(9)
I only defined the "build.gradle" file. Then, I just used the gradle command (you can use it if you installed Gradle) If I remember well, the configuration is easier with the last JavaFX versions, thanks to the JavaFX plugin in Gradle (openjfx.io/openjfx-docs/#gradle). So all these steps are probably useless now.Digestant
We can especially use jlink to get an application file (section "Runtime images")Digestant
I don't understand. After applying the plugin, setting the build.gradle I just have to run jLink task and what should I distribute to my clients? The "build" folder? I'm confusedLavinialavinie
With the method above (not jlink), the jar can be found in build/libs. With jlink, I suppose that you can find the generated distributable file (a zip file containing a .bat and a .sh file to run the app, if I remember well) in a subfolder of build.Digestant
Yeah. I don't need to have JRE at my client's pc?Lavinialavinie
You need to have one if you create a jar file (method without jlink). With jlink, you don't need one (it includes a "light" JRE containing only the Java core modules that you need)Digestant
Thanks, Flpe. I'm using Jlink but having some troubles to distribuite it to WindowsLavinialavinie
I made an example using Jlink. If you are interested, please check the new answer I will add to this questionDigestant
@Digestant I'm not having any success with getting this working. Without adding any Groovy complications, I find gradle run doesn't work and gradle createJar runs but the java command just hangs. Error from gradle run is "Error: JavaFX runtime components are missing, and are required to run this application". I get this both in W10 and Linux, and I have tried both Eclipse and Intellij.Willdon

© 2022 - 2024 — McMap. All rights reserved.