Build Standalone Installers for Spring Boot Apps with JPackage
This guide walks through using JPackage (standard from JDK 16) to create native Windows, macOS, and Linux installers for Spring Boot applications, covering JDK requirements, platform‑specific tools, custom JRE creation with jlink, Maven integration, and step‑by‑step command examples.
Introduction
Since JDK 14, Java includes the JPackage tool (standardized in JDK 16) that can package Java applications into platform‑specific native installers with a bundled custom JRE, eliminating the need for users to pre‑install a Java runtime.
JPackage Overview
JPackage resides in $JAVA_HOME/bin and can generate installers such as .exe / .msi for Windows, .dmg / .pkg for macOS, and .deb / .rpm for Linux. It also supports creating a trimmed JRE via jlink, which reduces installer size.
Traditional deployment requires a pre‑installed JRE/JDK, risking version mismatches and manual start‑script maintenance.
JPackage bundles a specific JRE, ensures consistent runtime, auto‑generates launchers, and produces one‑click installers for all major platforms.
Environment Preparation
JDK version : Use JDK 17 or newer (JPackage becomes a standard feature in JDK 16, and JDK 17 is the current LTS). jpackage --version Platform‑specific tools :
Windows – install WiX Toolset 3.11+ (provides .msi generation) and add its bin directory to PATH.
macOS – install Xcode command‑line tools ( xcode-select --install) for .dmg / .pkg creation.
Linux – Debian/Ubuntu need fakeroot; RedHat/CentOS need rpm-build.
Spring Boot Project Preparation
Assume a standard Spring Boot layout:
my-springboot-app/
├── src/
│ └── main/
│ ├── java/
│ └── resources/
├── pom.xml
└── target/
└── my-app-1.0.0.jarBuild Executable JAR
Package the application with Maven or Gradle so that the resulting JAR is executable (Spring Boot’s default packaging).
# Maven
mvn clean package
# Gradle
gradle clean buildPackaging with JPackage
Basic Windows command example:
jpackage \
--input target \
--name MySpringBootApp \
--main-jar my-app-1.0.0.jar \
--main-class org.springframework.boot.loader.JarLauncher \
--type msi \
--app-version 1.0.0 \
--vendor "My Company" \
--description "Enterprise Spring Boot application" \
--icon src/main/resources/app-icon.ico \
--win-dir-chooser \
--win-menu \
--win-shortcutKey parameters: --input: directory containing the JAR and dependencies. --name: application name. --main-jar: the executable JAR file. --main-class: entry point (Spring Boot uses org.springframework.boot.loader.JarLauncher). --type: installer type ( msi, exe, dmg, pkg, deb, rpm). --app-version, --vendor, --description, --icon: metadata.
Windows‑specific flags: --win-dir-chooser, --win-menu, --win-shortcut.
Custom JRE with jlink
To shrink the installer, create a custom runtime image that contains only the modules required by the application.
Step 1 – List required modules : jdeps --list-deps target/my-app-1.0.0.jar Sample output:
java.base
java.logging
java.sql
java.naming
java.desktop
...Step 2 – Build custom JRE (replace module list with the actual output):
jlink \
--add-modules java.base,java.logging,java.sql,java.naming,java.desktop,java.xml,java.management \
--output custom-jre \
--strip-debug \
--no-header-files \
--no-man-pages \
--compress=2Step 3 – Package using the custom runtime :
jpackage \
--input target \
--name MySpringBootApp \
--main-jar my-app-1.0.0.jar \
--main-class org.springframework.boot.loader.JarLauncher \
--type msi \
--runtime-image custom-jre \
--app-version 1.0.0 \
--vendor "My Company"Note: Spring Boot applications often depend on many modules; it is advisable to first package without trimming the JRE, verify functionality, and then optimise.
Platform‑Specific Packaging Examples
Windows (MSI):
jpackage \
--input target \
--name MyApp \
--main-jar my-app-1.0.0.jar \
--main-class org.springframework.boot.loader.JarLauncher \
--type msi \
--app-version 1.0.0 \
--icon src/main/resources/app.ico \
--win-dir-chooser \
--win-menu \
--win-shortcut \
--win-menu-group "My Application"macOS (DMG):
jpackage \
--input target \
--name MyApp \
--main-jar my-app-1.0.0.jar \
--main-class org.springframework.boot.loader.JarLauncher \
--type dmg \
--app-version 1.0.0 \
--icon src/main/resources/app.icns \
--mac-package-name "com.mycompany.myapp" \
--mac-package-identifier "com.mycompany.myapp"Linux (DEB):
jpackage \
--input target \
--name myapp \
--main-jar my-app-1.0.0.jar \
--main-class org.springframework.boot.loader.JarLauncher \
--type deb \
--app-version 1.0.0 \
--icon src/main/resources/app.png \
--linux-shortcut \
--linux-menu-group "Development"Integrate JPackage into Maven Build
Add the exec-maven-plugin to pom.xml so that the JPackage command runs during the package phase:
<build>
<plugins>
<!-- Spring Boot Maven plugin -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<!-- JPackage execution plugin -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>jpackage</id>
<phase>package</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>jpackage</executable>
<arguments>
<argument>--input</argument>
<argument>target</argument>
<argument>--name</argument>
<argument>MySpringBootApp</argument>
<argument>--main-jar</argument>
<argument>${project.build.finalName}.jar</argument>
<argument>--main-class</argument>
<argument>org.springframework.boot.loader.JarLauncher</argument>
<argument>--type</argument>
<argument>msi</argument>
<argument>--app-version</argument>
<argument>${project.version}</argument>
<argument>--vendor</argument>
<argument>My Company</argument>
<argument>--win-dir-chooser</argument>
<argument>--win-menu</argument>
<argument>--win-shortcut</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>Run the build: mvn clean package After the Maven build finishes, the native installer appears in the project root.
Conclusion
JPackage provides a powerful, script‑free way to distribute Java applications as native installers across Windows, macOS, and Linux. By integrating the tool into Maven and optionally creating a custom JRE with jlink, developers can balance installer size, deployment simplicity, and runtime performance to deliver the best user experience.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
