Issue
I have a maven project that I want to export into a runnable .jar file.
In VSCode the project works without a problem. The exported project includes a .jar file and a libs
folder with all javaFX files:
Documents/prosjekt/target
├── classes
├── generated-sources
├── generated-test-sources
├── libs
│ ├── apiguardian-api-1.1.0.jar
│ ├── javafx-base-18.0.1-mac-aarch64.jar
│ ├── javafx-base-18.0.1.jar
│ ├── javafx-controls-18.0.1-mac-aarch64.jar
│ ├── javafx-controls-18.0.1.jar
│ ├── javafx-fxml-18.0.1-mac-aarch64.jar
│ ├── javafx-fxml-18.0.1.jar
│ ├── javafx-graphics-18.0.1-mac-aarch64.jar
│ ├── javafx-graphics-18.0.1.jar
│ ├── javafx-media-18.0.1-mac-aarch64.jar
│ ├── javafx-media-18.0.1.jar
│ ├── junit-jupiter-api-5.7.0.jar
│ ├── junit-jupiter-engine-5.7.0.jar
│ ├── junit-platform-commons-1.7.0.jar
│ ├── junit-platform-engine-1.7.0.jar
│ └── opentest4j-1.2.0.jar
├── maven-archiver
├── maven-status
├── surefire-reports
├── prosjekt_boilerplate-1.0-SNAPSHOT.jar
└── test-classes
The problem occurs when I run the following command:
❯ java -jar /Users/user/Documents/prosjekt/target/prosjekt_boilerplate-1.0-SNAPSHOT.jar
Error: JavaFX runtime components are missing, and are required to run this application
I don't understand why I get this error, since all the JavaFX files are located and visible in the libs
folder.
I currently use Java Temurin 18.0.1 from SDKman.
❯ java -version
openjdk version "18.0.1" 2022-04-19
OpenJDK Runtime Environment Temurin-18.0.1+10 (build 18.0.1+10)
OpenJDK 64-Bit Server VM Temurin-18.0.1+10 (build 18.0.1+10, mixed mode)
Pom.xml
file:
<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>project</groupId>
<artifactId>prosjekt_boilerplate</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>prosjekt_boilerplate</name>
<dependencies>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-fxml</artifactId>
<version>18.0.1</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-media</artifactId>
<version>18.0.1</version>
</dependency>
<!-- JUnit 5 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.7.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<release>17</release>
</configuration>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version>
<configuration>
<argLine>--enable-preview</argLine>
</configuration>
</plugin>
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<version>3.0.0-M5</version>
<configuration>
<argLine>--enable-preview</argLine>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>prepare-package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>
${project.build.directory}/libs
</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>libs/</classpathPrefix>
<mainClass>
asteroids.AsteroidsApp
</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
</plugins>
</build>
</project>
Edit
I tried downloading the JavaFX SDK and running the following command:
❯ java -jar --module-path /Users/user/Downloads/javafx-sdk-18.0.1/lib \
--add-modules=javafx.controls,javafx.media,javafx.fxml,javafx.graphics \
/Users/user/Documents/prosjekt/target/prosjekt_boilerplate-1.0-SNAPSHOT.jar
and it worked! But it still doesn't answer my original question.
Solution
Although this is a long answer for explanatory purposes, the fix is actually very simple.
The only thing additionally needed is to place the jars with the modules on the module path rather than the classpath.
Answering your question to explain why your original approach fails.
The problem occurs when I run the following command:
java -jar /Users/user/Documents/prosjekt/target/prosjekt_boilerplate-1.0-SNAPSHOT.jar
Error: JavaFX runtime components are missing, and are required to run this application
I don't understand why I get this error, since all the JavaFX files are located and visible in the libs folder.
The reason you get this error is that you are running all code off of the classpath rather than the module path.
Modern JavaFX versions are not designed or supported to be run from the classpath. They should be run from the module path.
You have done the following things, which will make the program execute only on the classpath:
- Created a jar with your application.
- Created a manifest in the jar which specifies your execution class.
- Added a library path to the manifest.
Background Info
Your application classes don't need to be modular and other (non-JavaFX) libraries that are able to be executed from the classpath rather than the module path will function when placed on the classpath, but the JavaFX libraries will fail.
The solution is to put, at a minimum, the JavaFX classes on the module path. Ideally, you might also make your application modular (define a module-info.java
file for it) and use only non-automatic modular jars (those which also define a module-info.java
) in your application. But the ideal is not required (unless you want to package your application using jlink), only having JavaFX classes on the module path is required.
You cannot (currently) add other jars or folders to the module path via a jar manifest as you can currently do with the classpath. For information on this see the answer to this question (and the comments on this answer):
Instead, you need to specify the location of modules you add in the command line (or rely on a JRE image created by jlink that pre-bundles the required modules).
Additionally, if your application is modular you should use the -m
switch to specify the main class qualified by the module it is in and your application jar should also be on the module path. If your application is not modular, then you can still use the -jar
specifier and encode the main class name in the jar manifest rather than as a command-line switch.
Examples
So, let's say you have your application distributed as a zip that contains your app jar and dependent libraries in a sub-folder named lib.
For these examples, the application jar is a normal thin jar (it is not a fat jar which has been shaded or includes JavaFX runtime code).
For a non-modular application, this is discussed in eden coding. After the user has unzipped your package, and assuming the main class name is encoded in the manifest, then the application can be run via:
java -jar myJar.jar --module-path lib --add-modules javafx.controls,javafx.fxml
If you use additional JavaFX modules like javafx-media which are not transitive dependencies, then also include these.
If your application is modular, then you don't need to encode extra info in the jar manifest, but you do need to include a module-info.java
in your jar file. When doing this it is probably best to put your jar in the same lib directory as the rest of the modules you are using so that you don't need to also add it separately to the module path. After doing this, you can run the application like this (this example is from the jenkov tutorial on Java modules):
java --module-path lib -m com.jenkov.mymodule/com.jenkov.mymodule.Main
You can use a jar command to set a main class to run the modular jar file (also demonstrated in the jenkov tutorial). Then you can run that modular jar without providing the main class explicitly, but specifying the jar file and the module path (module path is still needed for JavaFX apps):
jar -c --file=lib/com-jenkov-mymodule.jar --main-class=com.jenkov.mymodule.Main -C out/com.jenkov.mymodule .
java --module-path lib -jar lib/com-jenkov-javafx.jar
I don't know exactly what the jar command does to put add the execution commands into the jar file, but I don't think it uses the manifest to do this. At the moment, I don't think the maven jar command is capable of doing this correctly (I think it just puts info in the manifest and not modular main class execution info in the jar).
In summary using Java modules and using the classpath is different and confusing . . . :-)
Hack
There is (for Java 17 at least) a hack to create a separate launcher class that bypasses the runtime check to allow the startup of the JavaFX framework from the module path rather than the classpath, but it is not supported. I don't recommend it, so I won't explain it further here.
Further recommendations
Because the command line switches are easy to get wrong or mistype, it is best when deploying JavaFX applications to provide a platform-specific execution script (unix/mac shell script or windows batch file) that a user can more easily use to execute the application.
Also consider distributing with a Java runtime, either via a zipped jlink image or a jpackage created installer. That makes it easier on your users because then they don't need to find and install an appropriate java runtime to use your application. As a bonus, jlink can create an example shell script to execute your application. Doing this via maven is easy, using the jlink option of the maven-javafx-plugin and specifying the jlinkZipName.
jpackage can create an OS level icon the user can just click on to run the app, but can't be used directly from the maven-javafx-plugin.
Additional resources
Further info including links to packaging options like JPackageScriptFX in the javafx tag info packaging resources.
Related question: How can I create an all-in-one jar for a JavaFX project with the Maven Assembly plugin?
Answered By - jewelsea
Answer Checked By - Dawn Plyler (JavaFixing Volunteer)