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 requirementsconf/
- Configuration filesinclude/
- Header fileslegal/
- License fileslib/
- Dependenciesrelease
- 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.
- You need to download the JavaFX modules from https://gluonhq.com/products/javafx/ (e.g.
javafx-jmods-13
) - When running
jlink
you need to tell it where to find the modules you extracted with the--module-path
directory - 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.