How to Create Java Runtime Images with jlink

Advertisement

Advertisement

Intro

Before Java 9 and Project Jigsaw, you would have to package the entire monolithic Java Runtime Environment (JRE) to distribute your application with an embedded JRE runtime. This took up a lot of space. Now, you can generate your own custom runtime with only the modules you want to keep the size of the final package smaller.

You can think of a runtime image as another name for a Java runtime environment (JRE). When you create a custom runtime, you are generating a new JRE that only includes the modules you want. You can exclude core java modules that you don't need and you can include custom modules that you wrote. They will all be "baked in" to the runtime.

If you are familiar with Python Virtual Environments, it is similar to that. It is a directory that contains an isolated Java environment with only the specific dependencies needed that will not conflict with other isolated runtimes.

After following this guide, you will understand how to use jlink to create runtime images with only the modules you want. You should also know how to add custom modules including third-party modules like JavaFX.

Simplest jlink example

To get a basic understanding of what jlink does, try running these basic commands.

This first example will create a runtime image with only the most basic modules for the core language.

# Create a runtime image with minimal modules
jlink --output my-minimal-runtime --add-modules java.base

It generates a directory named minimal-runtime/ that will contain a few subdirectories:

  • bin/ - All the necessary dynamic libraries, java executable, and other requirements
  • conf/ - Configuration files
  • include/ - Header files
  • legal/ - License files
  • lib/ - Dependencies
  • release - Contains info about the Java version

These runtime images are system-specific. For example, in Windows, the bin/ directory contains java.exe which will only work on Windows. You can use these runtime images packaged with your application to distribute the smallest package possible.

See what modules a runtime includes

You can see what modules a Java runtime includes by running the java command with the --list-modules option. For example, if you followed the previous step and created my-minimal-runtime/ then you could run:

my-minimal-runtime/bin/java --list-modules

Choose which modules to include

This example shows how to include multiple modules. In this case, the java.base and java.logging modules.

# Create an image with just base and logging modules
jlink --output minimal-with-logging --add-modules java.base,java.logging

This example would create a runtime image that contained all the modules in Java SE. This might be convenient but could also have unnecessary dependencies.

# Create a runtime image with all Java SE modules
jlink --output my-custom-jdk-runtime --add-modules java.se

To see a visual representation of how modules depend on each other, check out this blog post on TechTalks | Java 9 : Future 3.

Add custom modules

To add any custom modules that you created, just make sure you have the classes or the jar files necessary in the --module-path option and the module name is listed in the --add-modules option. For example:

# Multiple modules are separate by comma
# Multiple module paths are separated by semi-colon
jlink --output myapp --add-modules mymod1,mymod2 --module-path .;target/my.jar

Run a module using the custom runtime

Once you have the runtime built with the modules included, you can run any module using the java executable. This assumes you did not use the --strip-native-commands option which would remove the java.exe from bin/.

This example would run a class from a module in the runtime.

myruntime/bin/java --module mymodule/com.devdungeon.MainWindow

To run a JAR that is not already packed in to the runtime, add the JAR to the module path and then specify the module as normal. If the JAR has a main class defined in the manifest, you can run it directly.

# Run a module from a JAR using a custom runtime
myruntime/bin/java --module-path .;mylib.jar --module mymodule/MyClass
# Or if the JAR has a main class defined in the JAR manifest
myruntime/bin/java -jar mylib.jar

Remember, if you need to check what modules a runtime has you can use java --list-modules.

See what modules a JAR depends on

If you want to see what modules a JAR depends on so you know what modules to include in the runtime, you can use the jdeps tool like this:

jdeps mylib.jar

Building a runtime for Swing

If you want to make Swing GUI application you will need to depend on the java.desktop module.

# Create a runtime image with required components
# to use Swing desktop GUI
jlink --output jdk-with-swing --add-modules java.desktop

Building a runtime for Java FX

In order to build with JavaFX you need to take some extra steps.

  1. You need to download the JavaFX modules from https://gluonhq.com/products/javafx/ (e.g. javafx-jmods-13)
  2. When running jlink you need to tell it where to find the modules you extracted with the --module-path directory
  3. You need to tell jlink to include the modules with --add-modules.

Example:

jlink --output jdk-with-fx --module-path C:\opt\javafx-jmods-13.0.1 --add-modules javafx.media,javafx.web,javafx.fxml,java.logging

Generate native launch scripts

You can specify a native launcher by adding the --launcher option along with the module/class name to use as the main class. For example:

jlink --output myapp --module-path mylib.jar --add-modules mymodule --launcher mylauncher=mymodule/com.devdungeon.MainWindow

This will create a launcher named mylauncher (e.g. mylauncher.bat) using the module/class specified. The module name in --add-modules and in the launcher must match the module name in your module-info.java file. The launcher format is like this: ModuleName/java.package.name.MainClass.

jlink --output myapp --add-modules Mavenproject4 --launcher myLauncher=mymodule/com.devdungeon.mavenproject4.MainWindow --module-path target\mavenproject4-1.0-SNAPSHOT.jar

You can then run your class by simply running the native launcher (Windows example):

myapp\bin\myLauncher.bat

You can also create multiple launcher scripts for different classes by adding multiple --launcher options.

Strip out even more stuff

There are a few options that might help reduce file sizes even more:

--strip-debug
--strip-native-commands
--compress 2
--no-header-files
--no-man-pages

Package native installers with jpackage

Using jlink alone, you have a package that you could distribute with native launchers. From here, you could use a tool like InnoSetup to create an MSI installer or build your or .deb package, but in Java 14+, the new jpackage comes out-of-the box. The jpackage tool generates distributable packages for native systems including:

  • Windows .exe and .msi installers
  • Mac .pkg and .dmg installers
  • Linux .deb and .rpm packages for Debian and RedHat based distros

jpackage will run jlink and generate a runtime image, or you can bring your own image that you created yourself with jlink.

To learn more about how easy the jpackage tool is check out this great talk on YouTube Java Packaging Tool: Create Native Packages to Deploy Java Applications by Kevin Rushforth.

Conclusion

After following this guide, you should understand how to use jlink to create simple runtime images with only the modules you want. You should also know how to add multiple modules including third-party modules like JavaFX.

References

Advertisement

Advertisement