We are developing an JavaFX 11 application with the Adopt JDK 11 and IntelliJ using gradle. At the end we need wanted an EXE file for windows (the application is only designed for windows). In the first try we also used launch4J within gradle, due the following problem we would also be fine using a BAT file which we could migrate to an exe file instead.
So our main goal and question is, how can we create a executable jar file.
We made several approaches with different results and we are completly lost.
FAT JAR
We made a standalone application for testing purpose:
package de.test;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Main extends Application {
public static void main(String[] args) {
launch();
}
@Override
public void start(Stage primaryStage) throws Exception {
VBox vBox = new VBox();
Button button = new Button("Klick");
TextField textfield = new TextField();
TextArea area = new TextArea();
area.setMinHeight(300);
button.setOnAction(event -> area.setText(area.getText() + " -- Klick Version 1.0.8"));
vBox.getChildren().addAll(button, textfield,area);
primaryStage.setScene(new Scene(vBox));
primaryStage.setTitle("Test");
primaryStage.show();
}
}
FAT JAR
And our first try was a FAT-JAR with or without a modules-info.java file using the javafx plugin from gradle. Here is our gradle file
plugins {
id 'java'
id 'application'
id 'org.openjfx.javafxplugin' version '0.0.8'
}
group 'de.test'
version '1.0.8'
sourceCompatibility = 11
javafx {
modules = ['javafx.controls']
}
mainClassName = 'de.test.Main'
jar {
manifest {
attributes 'Main-Class': mainClassName
}
from {
configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
}
}
repositories {
mavenLocal()
mavenCentral()
}
After running gradle clean jar we tried to execute the jar with the following command java -jar ApplicationTest-1.0.8.jar the result:
Error: Could not find or load main class de.test.Main Caused by: java.lang.NoClassDefFoundError: javafx/application/Application
FAT JAR with dependencies and module-info.java
Then we tried to add the dependencies for JavaFX like this inside the gradle file
compile group: 'org.jboss.resteasy', name: 'resteasy-client', version: '4.3.0.Final'
compile "org.openjfx:javafx-graphics:11.0.2:win"
compile "org.openjfx:javafx-base:11.0.2:win"
compile "org.openjfx:javafx-controls:11.0.2:win"
compile "org.openjfx:javafx-fxml:11.0.2:win"
compile "org.openjfx:javafx-graphics:11.0.2:win"
And this is the module-info.java discriptor we added to the package de.test:
module ApplicationTest.main {
requires javafx.controls;
exports de.test;
}
When calling gradle installDist we used this command
cd build\install\ApplicationTest\lib java --add-modules "javafx.controls" --module-path . -jar ApplicationTest-1.0.8.jar
At least the application started at this point, but the layout was destroyed with the following info messages:
Sep. 20, 2019 8:32:55 VORM. com.sun.javafx.css.StyleManager loadStylesheetUnPrivileged INFO: Could not load stylesheet: com/sun/javafx/scene/control/skin/modena/modena.css Sep. 20, 2019 8:32:55 VORM. com.sun.javafx.css.StyleManager loadStylesheetUnPrivileged INFO: Could not load stylesheet: com/sun/javafx/scene/control/skin/modena/modena.css
By the way: IntelliJ is always working when using the command gradle clean run
JLink with module-info.java
The next approach was using jlink and the following modules-info.java
module ApplicationTest.main {
requires javafx.controls;
requires javafx.graphics;
exports de.test;
}
We needed to extend the gradle file a bit as the following
plugins {
id 'java'
id 'application'
id 'org.openjfx.javafxplugin' version '0.0.8'
id 'org.beryx.jlink' version '2.10.4'
}
group 'de.test'
version '1.0.8'
sourceCompatibility = 11
mainClassName = 'de.test.Main'
javafx {
version = 11
modules = [ 'javafx.controls']
}
jlink {
options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages']
}
repositories {
mavenLocal()
mavenCentral()
}
When using gradle jlink and executing the cd build\image\bin\ApplicationTest.bat file, the application starts WITH layout.
At this point we where kind of happy. Now we needed to add some dependencies for our application.
Using the following dependencies worked perfectly
dependencies {
compile "commons-io:commons-io:2.6"
compile "org.apache.logging.log4j:log4j-api:2.11.2"
compile "dom4j:dom4j:1.6.1"
compile "commons-lang:commons-lang:2.6"
compile "axis:axis:1.4"
compile "jaxen:jaxen:1.1.6"
compile "net.java.dev.jna:platform:3.5.2"
compile "org.apache.poi:poi:4.1.0"
compile "org.apache.poi:poi-scratchpad:4.1.0"
compile "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.9.4"
}
But as soon as I add the other dependencies I get different kind of error messages. I tried each dependency alone to isolate the error message.
org.jboss.resteasy - resteasy-client - 3.7.0.Final
Cannot derive uses clause from service loader invocation in: javax/ws/rs/client/FactoryFinder.find().
Cannot derive uses clause from service loader invocation in: javax/ws/rs/ext/FactoryFinder.find().
Cannot derive uses clause from service loader invocation in: javax/ws/rs/sse/FactoryFinder.find().
Cannot derive uses clause from service loader invocation in: javax/xml/bind/ServiceLoaderUtil.firstByServiceLoader().
ApplicationTest\build\jlinkbase\tmpjars\de.test.merged.module\module-info.java:154: error: the service implementation type must be a subtype of the service interface type, or have a public static no-args method named "provider" returning the service implementation
provides javax.ws.rs.ext.Providers with org.jboss.resteasy.plugins.interceptors.CacheControlFeature,
^
ApplicationTest\build\jlinkbase\tmpjars\de.test.merged.module\module-info.java:155: error: the service implementation type must be a subtype of the service interface type, or have a public static no-args method named "provider" returning the service implementation
org.jboss.resteasy.plugins.interceptors.encoding.ClientContentEncodingAnnotationFeature,
^
ApplicationTest\build\jlinkbase\tmpjars\de.test.merged.module\module-info.java:156: error: the service implementation type must be a subtype of the service interface type, or have a public static no-args method named "provider" returning the service implementation
org.jboss.resteasy.plugins.interceptors.encoding.MessageSanitizerContainerResponseFilter,
^
...
^
25 errors
Dependency: org.apache.logging.log4j:log4j-core:2.11.2
package org.apache.logging.log4j.spi is not visible
(package org.apache.logging.log4j.spi is declared in module org.apache.logging.log4j, but module de.test.merged.module does not read it)
package org.apache.logging.log4j.message is not visible
(package org.apache.logging.log4j.message is declared in module org.apache.logging.log4j, but module de.test.merged.module does not read it)
Dependency: org.jboss.resteasy:resteasy-multipart-provider:3.7.0.Final
module not found: java.activation
Dependecyy: org.jboss.resteasy:resteasy-jackson2-provider:3.7.0.Final
package javax.ws.rs.ext does not exist
package javax.ws.rs.ext does not exist
package javax.ws.rs.ext does not exist
Conclusion
We're not getting anywhere right now. It probably has something to do with modules-info.java, but the instructions and documentation on this topic are quite complex and often very detailed. We just wanted an application that could be started outside the IntelliJ.
We are open for all solutions as long as they work. We don't need a state of the art solution either, but something from which we can somehow get an EXE in the end (also with the help of an external tool). Our application doesn't have to be modular, but we neither get it nor solve it.
EDIT 1) FAT Jar with 2nd main class
Like in this aricle we added a second main java class without extending Application and put that to the gradle file
package de.test;
public class Main {
public static void main(String[] args) {
MainFX.main(args);
}
}
and the other class looks like this:
package de.test;
import ...;
public class MainFX extends Application {
public static void main(String[] args) {
launch();
}
@Override
public void start(Stage primaryStage) throws Exception {
...
}
}
and the gradle file looks like this:
plugins {
id 'java'
id 'application'
}
group 'de.test'
version '1.0.8'
sourceCompatibility = 11
mainClassName = 'de.test.MainFX'
jar {
manifest {
attributes 'Main-Class': mainClassName
}
from {
configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
}
}
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
compile "commons-io:commons-io:2.6"
compile "org.apache.logging.log4j:log4j-api:2.11.2"
compile "dom4j:dom4j:1.6.1"
compile "commons-lang:commons-lang:2.6"
compile "axis:axis:1.4"
compile "jaxen:jaxen:1.1.6"
compile "net.java.dev.jna:platform:3.5.2"
compile "org.apache.poi:poi:4.1.0"
compile "org.apache.poi:poi-scratchpad:4.1.0"
compile "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.9.4"
compile "org.jboss.resteasy:resteasy-multipart-provider:3.7.0.Final"
compile "org.jboss.resteasy:resteasy-jackson2-provider:3.7.0.Final"
compile "org.apache.logging.log4j:log4j-core:2.11.2"
compile "org.jboss.resteasy:resteasy-client:3.7.0.Final"
compile "org.openjfx:javafx-graphics:11.0.2:win"
compile "org.openjfx:javafx-base:11.0.2:win"
compile "org.openjfx:javafx-controls:11.0.2:win"
compile "org.openjfx:javafx-fxml:11.0.2:win"
compile "org.openjfx:javafx-graphics:11.0.2:win"
}
The result is really strange java -jar ApplicationTest.jar returns
Error: Could not find or load main class de.test.MainFX Caused by: java.lang.ClassNotFoundException: de.test.MainFX
We double checked the jar file and the class files are in the correct location (de\test\Main.class and de\test\MainFX.class)
EDIT 2) FAT Jar with 2nd main class
Based on this article we added the javafx plugin again, removed the javafx dependencies and defined the Launcher-Main inside the manifest and the regular FXML Mail as mainClassName:
...
mainClassName = 'de.test.MainFX'
javafx {
version = 11
modules = [ 'javafx.controls', 'javafx.fxml', 'javafx.web']
}
jar {
manifest {
attributes 'Main-Class': 'de.test.MainLauncher'
}
from {
configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
}
}
The result is
> Exception in thread "main" java.lang.NoClassDefFoundError:
> javafx/application/Application
> at java.base/java.lang.ClassLoader.defineClass1(Native Method)
> at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1016)
> at java.base/java.security.SecureClassLoader.defineClass(SecureClassLoader.java:174)
> at java.base/jdk.internal.loader.BuiltinClassLoader.defineClass(BuiltinClassLoader.java:802)
> at java.base/jdk.internal.loader.BuiltinClassLoader.findClassOnClassPathOrNull(BuiltinClassLoader.java:700)
> at java.base/jdk.internal.loader.BuiltinClassLoader.loadClassOrNull(BuiltinClassLoader.java:623)
> at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:581)
> at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
> at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
> at de.test.MainLauncher.main(MainLauncher.java:7) Caused by: java.lang.ClassNotFoundException: javafx.application.Application
> at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:583)
> at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
> at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
> ... 10 more
So we added the dependencies for javafx again with the following result:
Graphics Device initialization failed for : d3d, sw
Error initializing QuantumRenderer: no suitable pipeline found
java.lang.RuntimeException: java.lang.RuntimeException: Error initializing QuantumRenderer: no suitable pipeline found
at com.sun.javafx.tk.quantum.QuantumRenderer.getInstance(QuantumRenderer.java:280)
at com.sun.javafx.tk.quantum.QuantumToolkit.init(QuantumToolkit.java:222)
at com.sun.javafx.tk.Toolkit.getToolkit(Toolkit.java:260)
at com.sun.javafx.application.PlatformImpl.startup(PlatformImpl.java:267)
at com.sun.javafx.application.PlatformImpl.startup(PlatformImpl.java:158)
at com.sun.javafx.application.LauncherImpl.startToolkit(LauncherImpl.java:658)
at com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:678)
at com.sun.javafx.application.LauncherImpl.lambda$launchApplication$2(LauncherImpl.java:195)
at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: java.lang.RuntimeException: Error initializing QuantumRenderer: no suitable pipeline found
at com.sun.javafx.tk.quantum.QuantumRenderer$PipelineRunnable.init(QuantumRenderer.java:94)
at com.sun.javafx.tk.quantum.QuantumRenderer$PipelineRunnable.run(QuantumRenderer.java:124)
... 1 more
Exception in thread "main" java.lang.RuntimeException: No toolkit found
at com.sun.javafx.tk.Toolkit.getToolkit(Toolkit.java:272)
at com.sun.javafx.application.PlatformImpl.startup(PlatformImpl.java:267)
at com.sun.javafx.application.PlatformImpl.startup(PlatformImpl.java:158)
at com.sun.javafx.application.LauncherImpl.startToolkit(LauncherImpl.java:658)
at com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:678)
at com.sun.javafx.application.LauncherImpl.lambda$launchApplication$2(LauncherImpl.java:195)
at java.base/java.lang.Thread.run(Thread.java:834)