I'm developing an app using JavaFX, but I've encountered a problem creating a build and converting it into an executable app. Several years ago, I created a small app using this technology, and I remember that Intellij IDEA allowed me to create a native executable for almost any desktop operating system (Windows, Mac, or Linux). However, that strategy didn't work for me in this project. According to what I've been reading online, the JavaFX project was natively separated from the Oracle JDK, so it needs to be run independently.
I'm developing this new project with Oracle OpenJDK 23 and intending to use it only in Windows environments. I've been researching how to create an executable build of this app using Maven plugins (I've already tried some variants) and nothing has worked. I managed to get the resulting .jar file to be of shade type and contain all the libraries used by the project. I've attached my 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>cu.nerzur</groupId>
<artifactId>WallpaperEngine</artifactId>
<version>0.2.2</version>
<name>WallpaperEngine</name>
<description>WallpaperEngine</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<junit.version>5.10.2</junit.version>
<java.version>23</java.version>
</properties>
<dependencies>
<!-- reduce boiler plate -->
<dependency>
<groupId>com.sangupta</groupId>
<artifactId>jerry-core</artifactId>
<version>3.0.1</version>
</dependency>
<!-- for making HTTP calls -->
<dependency>
<groupId>com.sangupta</groupId>
<artifactId>jerry-http</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.38</version>
<optional>true</optional>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>5.13.0</version>
</dependency>
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna-platform</artifactId>
<version>5.13.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.openjfx/javafx-controls -->
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>26-ea+3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.openjfx/javafx-fxml -->
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-fxml</artifactId>
<version>26-ea+3</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-swing</artifactId>
<version>26-ea+3</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<!-- for JSON parsing -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.13.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.14.0</version>
<configuration>
<release>23</release>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.38</version>
</path>
</annotationProcessorPaths>
<compilerArgs>
<arg>-Xlint:all</arg>
</compilerArgs>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.6.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.nerzur.wallpaperengine.Init</mainClass>
</transformer>
</transformers>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.openjfx</groupId>
<artifactId>javafx-maven-plugin</artifactId>
<version>0.0.8</version>
<executions>
<execution>
<id>default-cli</id>
<configuration>
<mainClass>com.nerzur.wallpaperengine.Init</mainClass>
<launcher>app</launcher>
<jlinkZipName>app</jlinkZipName>
<jlinkImageName>app</jlinkImageName>
<noManPages>true</noManPages>
<stripDebug>true</stripDebug>
<noHeaderFiles>true</noHeaderFiles>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
But I still can't get the Java FX runtimes to be included, and I get an error message when running the jar with the command java -jar app.jar.
Error when execute the jar using java -jar command
Some solutions mention using a module-info.java file to create a Java module that includes JavaFX dependencies. However, when creating this file, errors start to appear because I'm using dependencies that aren't backed by modules, or so it seems. I'm not currently using this file, but when I had it integrated into the project, it looked something like this.
module com.nerzur.WallpaperEngine {
requires javafx.controls;
requires javafx.fxml;
requires javafx.graphics;
requires java.desktop;
requires transitive static lombok;
requires transitive slf4j.api;
requires transitive jerry.http;
requires transitive jerry.core;
requires transitive java.net.http;
requires transitive com.google.gson;
requires transitive com.sun.jna;
requires transitive com.sun.jna.platform;
opens com.nerzur.wallpaperengine to javafx.fxml;
opens com.nerzur.wallpaperengine.controller to javafx.fxml;
exports com.nerzur.wallpaperengine;
exports com.nerzur.wallpaperengine.controller;
exports com.nerzur.wallpaperengine.service;
exports com.nerzur.wallpaperengine.scheduledTask;
exports com.nerzur.wallpaperengine.util;
}
The only solution I've found to make this project compilable is to use Launch4j (an external app) using the current pom.xml and deleting the module-info.java file. Then, pass a fixed path for the JRE and another path with the JVM execution options as parameters with the following command: --module-path "C:\WallpaperEngine\libs\javafx-sdk-24.0.2\lib" --add-modules javafx.controls,javafx.fxml,javafx.swing"
which indicates the absolute path of the JavaFX SDK. This forces me to copy the app to an absolute path on the PC and include both SDKs in the compiled APK (which doesn't seem optimal to me).
Is there any other way to create an executable for this type of application that doesn't involve such drastic methods? Thanks in advance.
C:\WallpaperEngine
. Then the parameter should be adjusted to use a relative directory:.\libs\javafx-sdk-24.0.2\lib
-->--module-path ".\libs\javafx-sdk-24.0.2\lib"
--module-path "%EXEDIR%\libs\javafx-sdk-24.0.2\lib" --add-modules javafx.controls,javafx.fxml,javafx.swing