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.

Java Companion
Java Companion
Java Companion
Build Standalone Installers for Spring Boot Apps with JPackage

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.jar

Build 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 build

Packaging 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-shortcut

Key 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=2

Step 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.

mavenLinuxSpring BootwindowsmacOSJLinkjpackagenative installer
Java Companion
Written by

Java Companion

A highly professional Java public account

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.