Package a JavaFX Application as a Platform Specific Executable
Ana-Maria Mihalceanu on November 14, 2023JavaFX is a software platform that runs on top of a JDK and enables you to develop desktop applications, as well as rich web applications running across a broad range of devices. When your JavaFX application is ready, you can package it into a self-executable-jar and release it to your users. But this means that your users should have installed the same JDK version that you used when packaging your application. To avoid this situation, you can create a custom runtime image containing only the platform modules required by your application.
Compile a JavaFX application with jmods
Let’s consider the following HelloWorldFX.java
application that changes the color of the circle whenever is clicked:
package helloworld;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.scene.Group;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
public class HelloWorldFX extends Application {
@Override
public void start(Stage stage) {
String javaVersion = System.getProperty("java.version");
String javafxVersion = System.getProperty("javafx.version");
Circle circle = new Circle(300.0f, 200.0f, 50.0f);
circle.setFill(Color.BLUE);
circle.setStrokeWidth(20);
//Set text
Text text = new Text("Click on the circle to change its color");
text.setFont(Font.font(null, FontWeight.BOLD, 20));
text.setFill(Color.WHITE);
text.setX(150);
text.setY(50);
//Setup the mouse event handler
EventHandler<MouseEvent> eventHandler = new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent e) {
System.out.println(String.format("Hello World running on JavaFX %s on top of Java %s", javafxVersion, javaVersion));
if (circle.getFill().equals(Color.BLUE))
circle.setFill(Color.RED);
else
circle.setFill(Color.BLUE);
}
};
//Register the event filter
circle.addEventFilter(MouseEvent.MOUSE_CLICKED, eventHandler);
//Add circle and text to a group
Group root = new Group(circle, text);
Scene scene = new Scene(root, 640, 480);
scene.setFill(Color.WHITE);
stage.setTitle("Color shifter");
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
The following module-info.java
file defines the helloworldfx
module:
module helloworldfx {
requires javafx.controls;
exports helloworld;
}
You can run your JavaFX application using the set of jmod files available in the JavaFX release page. Download the archive appropriate for your operating system:
- Linux/x64
- macOS/x64
- macOS/AArch64
- Windows/x64
You can try the example application with JavaFX 21 or newer. In case your operating system is Linux/x64 or macOS, unpack the downloaded archive and add the environment variable pointing to the resulting jmods directory:
export PATH_TO_FX_JMODS=path/to/javafx-jmods-21.0.1
If you are a user of Windows operating system, unzip the downloaded archive and add the environment variable pointing to the resulting jmods directory:
set PATH_TO_FX_JMODS="path\to\javafx-jmods-21.0.1"
Let’s compile the application using JavaFX jmods:
- for Linux/x64, macOS/x64, macOS/AArch64
javac --module-path $PATH_TO_FX_JMODS -d mods/helloworldfx $(find . -name "*.java")
- for Windows
dir /s /b *.java > sources.txt & javac --module-path %PATH_TO_FX_JMODS% -d mods/helloworldfx @sources.txt & del sources.txt
Ship a self-contained JavaFX application as a platform specific executable
Because helloworldfx
is a modular application, you can use jpackage
to automatically generate a runtime image.
In case your local operating system is macOS, you can run the following command to build a native package in a dmg
format:
jpackage \
--dest output \
--name ShiftingCircle \
--type dmg \
--module-path $PATH_TO_FX_JMODS:mods \
--add-modules helloworldfx \
--module helloworldfx/helloworld.HelloWorldFX \
--mac-package-name ShiftingCircle \
--mac-package-identifier helloworld.HelloWorldFX \
--java-options -Xmx2048m
jpackage
will automatically run jlink
and generate a runtime with the modules needed.
The previous command builds a native macOS application (--type dmg
) in the output folder (--dest output
) with the name ShiftingCircle
.
To create this runtime image you should specify the path to JavaFX jmods (--module-path $PATH_TO_FX_JMODS:mods
), but also add the module containing HelloWorldFX.java
(--add-modules helloworldfx
).
The --module helloworldfx/helloworld.HelloWorldFX
option instructs the application launcher where to find the main class of the application.
As the resulting executable is a native macOS image, you can add a few Mac-specific options:
- define the name of the application as it appears in the Menu Bar (
--mac-package-name ShiftingCircle
) - when signing the application package, components that don’t have an existing package identifier will be prefixed by the value of
--mac-package-identifier
option.
If you are running on Windows operating system, you can ship the previous application as a Microsoft Installer (msi
) file.
Prior to running the jpackage
command, make sure that you have installed WiX tools.
Then use the following command in a Command Prompt
window:
jpackage
--dest output
--name ShiftingCircle
--type msi
--module-path "%PATH_TO_FX_JMODS%;mods"
--add-modules helloworldfx
--module helloworldfx/helloworld.HelloWorldFX
--win-shortcut --win-menu
--java-options -Xmx2048m
Running the previous command will create a ShiftingCircle-1.0.msi
Microsoft Installer file. Running ShiftingCircle-1.0.msi
will request to add a Start Menu
shortcut for the application and to create a desktop shortcut associated to it.
In conclusion, if you want to ship Java/JavaFX applications without requiring users to install a JDK,
consider using jpackage
to create native macOS, Windows, or Linux software.