I am a beginner for JavaFX. I have a JavaFX & Spring Boot application which runs as a STOMP Websocket client. I did build it as a jar file and it runs well with Java command. My question is how to wrap it in a Windows/Mac/Linux installer (including JRE) so that it can be installed easily on other computers. My pom.xml
<?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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.my-group</groupId>
<artifactId>my-app</artifactId>
<version>1.0</version>
<name>my-app</name>
<properties>
<java.version>17</java.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>5.2.1</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>19.0.2.1</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-fxml</artifactId>
<version>19.0.2.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
My main class
package com.mygroup.myapp;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import java.awt.*;
@SpringBootApplication
public class MyApplication extends Application {
private ConfigurableApplicationContext applicationContext;
private Parent rootNode;
public static void main(String[] args) {
if (!SystemTray.isSupported()) {
System.setProperty("java.awt.headless", "false");
}
Application.launch(args);
}
@Override
public void init() throws Exception {
applicationContext = SpringApplication.run(MyApplication.class);
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/login.fxml"));
fxmlLoader.setControllerFactory(applicationContext::getBean);
rootNode = fxmlLoader.load();
}
@Override
public void start(Stage primaryStage) {
Platform.setImplicitExit(false);
primaryStage.setResizable(false);
primaryStage.setScene(new Scene(rootNode));
primaryStage.show();
}
@Override
public void stop() {
applicationContext.close();
Platform.exit();
}
}
I have tried this tutorial using JPackage but it does not run after installation.
This example is only for creating a Windows Installer for a JavaFX SpringBoot application, using Maven and jpackage
running on Windows, with the assistance of IntelliJ Idea, though the steps will work for any Java application and can performed without Idea, (given some adaption and modification).
This is not a guide on how to integrate SpringBoot with JavaFX, but rather how to package and deploy such an application to a Windows machine.
There are a lot of steps here, the reason for that is:
As long as you follow this guide, you should be able to create an installer in a reasonable amount of time and have some chance of troubleshooting the build and installation process when something goes wrong.
Configure your JAVA_HOME with JDK 21+.
Install the JavaFX SDK 21.0.1+.
In Idea 2023.3.2 or later, create a new JavaFX project.
wininstalled
.Delete the module-info.java
file.
Upgrade the maven version in the maven wrapper of the generated project.
Edit <your project home>\.mvn\wrapper\maven-wrapper.properties
Ensure the version listed in the distributionUrl is at least 3.9.6.
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip
This is because the akman jpackage plugin used here requires at least that version of maven to work.
Replace the generated pom.xml file, with the one below.
Reimport the Maven project into Idea.
Go to the HelloApplication, and try to run it.
Edit the run configuration that your execution attempt generated so that you can set the VM arguments for it.
Set these values (adjust for additional or removed JavaFX modules as needed):
--add-modules javafx.controls,javafx.fxml --module-path <your JavaFX SDK path>/lib
Make sure you set VM arguments NOT program arguments.
Do actually click the link and study the image of how the VM arguments are set, so you don't make a mistake here.
Run the application again, it should run in the IDE.
Copy the icon file for your application from icon archive (Choose All Download formats | Download ICO
) to
/src/main/resources/com/example/wininstalled/coffee.ico
Go to the Maven panel in the IDE, your project name there should be the name configured in the pom.xml (wininstalled
), under Lifecycle
, double click clean
, then click install
.
Your application will be built and packaged.
target/jpackage-input
directory.jpackage
using the akman plugin configuration.jpackage
will create a jlinked image of the java runtime using a default set of modules and the modules listed in the akman configuration (these include both modules from the JDK and modules from JavaFX) and those will be baked into the image
jpackage
will copy the non-modular dependencies from the target/jpackage-input
directory and configure the application startup executable that it creates to run with the non-modular jars on the classpath.jpackage
will invoke Wix to create an installer for the application.
<your project>/target/jpackage/<your project name>.exe
file.To run the installer, select the Terminal tab in Idea
Execute the installer
.\target\jpackage\jpackage\wininstalled-24.01.1013.1128.exe
jpackage
configuration assigned Windows to use for the application update and to display when the user queries version information on the installed application in the OS.The installer will execute and install the application onto your machine.
The application is installed under:
\Users\<your username>\AppData\Local\<your app name>\<your app name>.exe
A shortcut to the application with the coffee icon you configured will be added to your desktop.
The app is also available from the Windows Start menu,
The app can be removed via the Windows Add or Remove Programs item in the Windows Control Panel.
You can double click your application icon to run your app.
You can run the application from the command line by executing the .exe
file which has been installed (not the installer exe, but the program exe).
When things go wrong, which is quite likely . . . for instance, maybe you click the icon of the installed application and nothing happens, and then you think you are stuck, but you are not . . .
<user dir>/AppData/Local/<your app name>/<your app name>.exe
file from the command line (this is the installed app, not the installer app). If it fails with a stack trace, you will now see the stack trace in the console explaining what went wrong.jpackage
documentation - official documentation from Oracle.Most of the steps described here are general for packaging any Java program and not specific for SpringBoot or JavaFX. So you can apply these steps for other application types with some modifications.
Even though the target here is SpringBoot, most of the information is generic to deploying a Java application on Windows using a Windows Installer and not that specific for SpringBoot. The only real complication with SpringBoot is that it works best with a non-modular application, but other than that there is nothing special about it in respect to creating installers.
Similarly, for JavaFX it is only supported when used as modules, so that is what is demonstrated here. Note that a non-modular application can still use JDK and JavaFX modules, so having a non-modular application that uses SpringBoot in a non-modular way, but uses a custom modular Java runtime and JavaFX modules is fine.
The steps described here are for a Windows-only build.
Steps to create installers for other platforms can be followed and are largely similar to the steps outlined here for Windows. However, they must be run on a machine of the same type as the target platform you are building for, and will create a platform-specific installer for that machine type.
The installers built by jpackage
will differ, not only by OS, but by architecture type (for instance a Mac Intel installer must be created on a Mac Intel machine, and a Mac M-series processor installer must be created on a Mac M-series machine).
The steps for other platforms will differ slightly (e.g. linux builds will use rpm
or deb
packaging rather than wix packaging, and mac
builds will create Mac-type package formats such as pkg
or dmg
). There are different requirements for some things like icon types and formats for different platforms, as well as possibly requirements for signing the app for distribution, which may require paid developer certificates. None of those topics are covered here, but there is information on them in the jpackage
resource guide from Oracle.
<?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>com.example</groupId>
<artifactId>wininstalled</artifactId>
<version>1.0-SNAPSHOT</version>
<name>wininstalled</name>
<description>An installable SpringBoot JavaFX application for Windows</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<javafx.version>21.0.1</javafx.version>
<jpackageInputDirectory>${project.build.directory}/jpackage-input</jpackageInputDirectory>
<maven.build.timestamp.format>yy.MM.ddHH.mmss</maven.build.timestamp.format>
</properties>
<dependencies>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>${javafx.version}</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-fxml</artifactId>
<version>${javafx.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>3.2.1</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.2.224</version>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<outputDirectory>${jpackageInputDirectory}</outputDirectory>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.6.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>
${jpackageInputDirectory}/lib
</outputDirectory>
<excludeGroupIds>
org.openjfx
</excludeGroupIds>
<includeScope>
runtime
</includeScope>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>21</source>
<target>21</target>
<parameters>true</parameters>
</configuration>
</plugin>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.3.1</version>
<executions>
<execution>
<id>auto-clean</id>
<phase>verify</phase>
<goals>
<goal>clean</goal>
</goals>
<configuration>
<excludeDefaultDirectories>true</excludeDefaultDirectories>
<filesets>
<fileset>
<directory>${project.build.directory}/jpackage</directory>
</fileset>
</filesets>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.github.akman</groupId>
<artifactId>jpackage-maven-plugin</artifactId>
<version>0.1.5</version>
<executions>
<execution>
<phase>verify</phase>
<goals>
<goal>jpackage</goal>
</goals>
<configuration>
<name>wininstalled</name>
<appversion>${maven.build.timestamp}</appversion>
<copyright>Unrestricted freeware</copyright>
<description>JavaFX windows installed app demo.</description>
<vendor>Acme Widgets, Inc.</vendor>
<icon>${project.basedir}/src/main/resources/com/example/wininstalled/coffee.ico</icon>
<modulepath>
<dependencysets>
<dependencyset>
<includeoutput>false</includeoutput>
<excludeautomatic>true</excludeautomatic>
<includes>
<!-- todo would it be better to fetch jmods and use them? -->
<include>glob:**/javafx-*-win.jar</include>
</includes>
</dependencyset>
</dependencysets>
</modulepath>
<addmodules>
<!-- we add required modules here,
we need to include base ones from the jdk which are not
part of the minimum service set that jpackage uses by default,
for example jdk.crypto.cryptoki is needed for ssl support and
jdk.crypto.ec if you need to support elliptic curve ciphers in ssl
and java.sql if you (or a library you use) uses jdbc, etc.
you would want different ones for another app,
libraries that are not treated as modular should need to be listed,
transitively included modules don`t need to be listed -->
<addmodule>jdk.crypto.cryptoki</addmodule>
<addmodule>jdk.crypto.ec</addmodule>
<addmodule>java.sql</addmodule>
<addmodule>java.naming</addmodule>
<addmodule>java.net.http</addmodule>
<addmodule>java.instrument</addmodule>
<addmodule>javafx.controls</addmodule>
<addmodule>javafx.fxml</addmodule>
<!-- if you want these other javafx modules then
uncomment them and ensure you
also have maven dependencies for them -->
<!-- <addmodule>javafx.media</addmodule>-->
<!-- <addmodule>javafx.swing</addmodule>-->
<!-- <addmodule>javafx.web</addmodule>-->
</addmodules>
<!-- our app is non-modular, so we wont have a module entry, we set the mainjar and mainclass instead -->
<!-- <module>com.example.wininstalled/HelloApplication</module>-->
<input>${jpackageInputDirectory}</input>
<mainjar>wininstalled-1.0-SNAPSHOT.jar</mainjar>
<mainclass>com.example.wininstalled.HelloApplication</mainclass>
<!-- <javaoptions>-Dfile.encoding=UTF-8</javaoptions>-->
<!-- <installdir>Utilities/Win Installed FX App</installdir>-->
<!-- <licensefile>${project.basedir}/config/jpackage/LICENSE</licensefile>-->
<!-- <resourcedir>${project.basedir}/config/jpackage/resources</resourcedir>-->
<!-- <windirchooser>false</windirchooser>-->
<winmenu>true</winmenu>
<!-- <winmenugroup>Utilities/Win Installed FX App</winmenugroup>-->
<winperuserinstall>true</winperuserinstall>
<winshortcut>true</winshortcut>
<!-- <winupgradeuuid>${project.build.uuid}</winupgradeuuid>-->
<!-- if something goes wrong (and it will..) enable the winconsole and run the app from the command line
then if the app aborts with an exception you can see it
To run from the command line execute
<your user home>\AppData\Local\<your app>\<your app>.exe
-->
<!-- <winconsole>true</winconsole>-->
<type>EXE</type>
<verbose>true</verbose>
<!-- example for setting jvm options if needed -->
<javaoptions>--enable-preview</javaoptions>
</configuration>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>9.5</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>3.2.1</version>
</plugin>
</plugins>
</build>
</project>