How to Package Spring Boot Apps into Native Installers with JPackage
This guide explains how to use the JPackage tool introduced in JDK 14/16 to create platform‑specific native installers for Spring Boot applications, covering environment setup, custom JRE creation with jlink, Maven integration, and examples for Windows, macOS, and Linux.
Introduction
Since JDK 14 (standard from JDK 16), the jpackage tool can bundle a Java application into a native installer with an embedded custom JRE, removing the need for a pre‑installed Java runtime.
jpackage Overview
What is jpackage
jpackage resides in $JAVA_HOME/bin and can generate native installers (.exe/.msi for Windows, .dmg/.pkg for macOS, .deb/.rpm for Linux). It can also create a custom runtime image via jlink.
Advantages
No pre‑installed JRE/JDK required.
Consistent runtime version.
Automatic launcher generation.
One‑click generation for all platforms.
Environment Preparation
JDK version
Use JDK 17 or newer; jpackage becomes standard in JDK 16.
jpackage --versionPlatform‑specific tools
Windows: Install WiX Toolset 3.11+ and add its bin directory to PATH.
macOS: Install Xcode command‑line tools with xcode-select --install.
Linux:
Debian/Ubuntu: sudo apt-get install fakeroot RedHat/CentOS:
sudo yum install rpm-buildSpring Boot Project Preparation
Project structure example
my-springboot-app/
├── src/
│ └── main/
│ ├── java/
│ └── resources/
├── pom.xml
└── target/
└── my-app-1.0.0.jarBuild executable JAR
# Maven
mvn clean package
# Gradle
gradle clean buildThe resulting JAR is executable by default in Spring Boot.
Packaging with jpackage
Basic command (Windows 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-shortcutParameter description
--input: Directory containing the JAR and its dependencies. --name: Application name. --main-jar: Main JAR file. --main-class: Entry class; for Spring Boot use org.springframework.boot.loader.JarLauncher. --type: Installer type (msi, exe, dmg, pkg, deb, rpm). --app-version: Version number. --icon: Platform‑specific icon file. --win-dir-chooser, --win-menu, --win-shortcut: Windows UI options.
Custom JRE with jlink
To reduce installer size, create a trimmed runtime containing only required modules.
Step 1: List required modules
jdeps --list-deps target/my-app-1.0.0.jarStep 2: Create custom JRE
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 with 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 JRE trimming and only optimise after confirming functionality.
Platform‑specific 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"Integration into Maven build
Add an exec-maven-plugin configuration to pom.xml so that jpackage runs during the package phase.
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</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 mvn clean package; the installer will be created in the project root.
Conclusion
jpackage enables distribution of Java applications as native installers, from simple command‑line usage to full Maven integration, allowing developers to balance installer size, deployment convenience, and runtime performance.
macrozheng
Dedicated to Java tech sharing and dissecting top open-source projects. Topics include Spring Boot, Spring Cloud, Docker, Kubernetes and more. Author’s GitHub project “mall” has 50K+ stars.
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.
