How to package and distribute Java applications is a long lasting issue in Java platform. Users want to have a smooth experience when installing Java applications. For example, on Windows platform, they want to simply double-click a file to install. There was no practical built-in tools to package Java applications. So we have to either distribute JAR files or using third-party tools like install4j.

JEP 343 provides an incubating tool jpackage to package self-contained Java applications. This JEP is part of JDK 14.

jpackage packages a Java application into a platform-specific package that includes all of the necessary dependencies. It supports different platform-specific package formats.

  • Linux - deb and rpm
  • macOS - pkg and dmg
  • Windows - msi and exe

Get JDK 14

To use jpackage, you need to have JDK 14. JDK 14 is not released yet. You can get early access builds from jdk.java.net.

It's recommended to use SDKMAN! to install JDK.

$ sdk install java 14.ea.30-open

You don't need to set JDK 14 as the default JDK. You can set JDK 14 only for a terminal session.

$ sdk use java 14.ea.30-open

Below is the output of java -version of JDK 14 used in this post.

openjdk version "14-ea" 2020-03-17
OpenJDK Runtime Environment (build 14-ea+30-1385)
OpenJDK 64-Bit Server VM (build 14-ea+30-1385, mixed mode, sharing)

Sample Application

We use a Spring Boot sample application created from start.spring.io as the example. It's a Maven-based simple Spring MVC app. We can use Maven command to package the application as the single JAR file demo-0.0.1-SNAPSHOT.jar.

Use jpackage

To create a package for the sample application, we put the demo-0.0.1-SNAPSHOT.jar into lib directory and run jpackage to package.

$ jpackage -n spring-demo -i lib --main-jar demo-0.0.1-SNAPSHOT.jar -d output

Note: The command above requires the JAR file to be executable, i.e. the MANIFEST.MF file has the attribute Main-Class. If the JAR file is not executable, you need to use --main-class to provide the main class. The JAR file demo-0.0.1-SNAPSHOT.jar created by Spring Boot Maven plugin is executable, so it can be used directly.

After running the command above, we can see the file spring-demo-1.0.dmg in output directory. We can double-click this DMG file to install. Easy!

DMG

Custom Runtime Image

For modular applications, you can use --module-path to provide application modules. The resulting package only contains modules required by the application.

$ jpackage --name myapp --module-path lib -m myapp

For non-modular applications, jpackage will include a lot of JDK modules, even though some of these modules are not required. To build a smaller package, you can use jlink to created a custom runtime image first, then use jpackage with --runtime-image option to specify the custom runtime image.

Use jdeps

The first step is to find out what JDK modules are required by the application. This can be done using jdeps. However, we cannot use the JAR file created by Spring Boot Maven plugin, because the JAR file uses a special format to organize dependencies. You should use Maven Dependency Plugin or Maven Assembly Plugin to collect JAR files of all dependencies into a directory.

Suppose that all JAR files are put into lib directory. We can use the following command to print out modules required by the application.

jdeps --class-path "lib/*" \
  --multi-release base \
  --ignore-missing-deps \
  -recursive \
  --print-module-deps \
  lib/demo-0.0.1-SNAPSHOT.jar

The output for the sample Spring application is shown as below.

java.base,java.desktop,java.instrument,java.management.rmi,java.naming,java.prefs,java.scripting,java.security.jgss,java.sql,jdk.httpserver,jdk.unsupported

The output can be used by jlink directly.

Use jlink

The second step is to create custom runtime image using jlink. In the command below, the value of --add-modules option comes from output of jdeps.

$ jlink --add-modules java.base,java.desktop,java.instrument,java.management.rmi,java.naming,java.prefs,java.scripting,java.security.jgss,java.sql,jdk.httpserver,jdk.unsupported --output spring-demo-runtime

After running the command above, the spring-demo-runtime contains the created runtime image.

Create Package

The last step is to create a package using the custom runtime image. The value of --runtime-image comes from the custom runtime image created in the last step.

$ jpackage -n spring-demo -i lib \
--main-jar demo-0.0.1-SNAPSHOT.jar -d output \
--runtime-image spring-demo-runtime

Application Arguments

If the application requires arguments when starting, you can use --arguments to specify. The --java-options option can pass options to JVM.

Package Metadata

jpackage also supports other options to provide package metadata.

  • --app-version <version>: App version
  • --copyright <copyright string>: Copyright
  • --description <description string>: Description
  • --license-file <file path>: License file
  • --name/-n <name>: App name
  • --vendor <vendor string>: Vendor.