home | src | pltl

Using jlink to Build Standalone Desktop Applications

Starting with Java 9, we can use the jlink tool to assemble and optimize a set of modules and their dependencies into a custom runtime image. This means that we can take any Java project (as long as it’s compiled into modules) and create a standalone application out of it. We can ship this application to users as a compressed archive and they can use it without ever knowing what java even is.

It may be of interest to note that Maven support for jlink is lacking even to this day. The maven-jlink-plugin is still in its first alpha and so, it’s probably advisable to just use jlink directly as we will do here.

In this example I’m assuming a project with one module named com.hello.world was built and put under Maven’s default build folder (target). A multi-module project would have followed the same scenario. Also, the JAVA_HOME variable needs to point to the appropriate JDK.

The jlink command looks something like this:

jlink
    --module-path target
    --add-modules com.hello.world
    --output target/output
    --launcher helloworld=com.hello.world/com.hello.world.Main
    --compress 2
    --strip-debug
    --no-header-files

Let’s elaborate on these options:

--module-path is a set of paths containing modules from which the final image will be built, separated by a separator char (that is ; on Windows and : on Unix). In our case the module path is just the path to our complied module (target).

Note: in earlier versions, the jdk modules had to be added to the module path as well (i.e.: $JAVA_HOME/jmods:target). Since Java 11, it seems that they are added by default.

--add-modules is a set of root modules that will be added to the image along with their dependencies. All of these modules have to be available from the module path, for obvious reasons.

--output is the path to the desired output folder the image will be created in.

--launcher specifies the name for the generated shell and batch scripts that will be used to run the application (In this case helloworld) along with the module (com.hello.world) and its main class (com.hello.world.Main) that will be invoked once the application starts. It is possible to omit the main class here if the module was originally complied with the --main-class option. You can also skip this altogether if you prefer to write your own launcher scripts.

--compress will compress the resulting image according to the specified compression level:

--strip-debug and --no-header-files are further optional optimizations that will slightly reduce the size of the final image.

It is also possible to provide an options file containing options you may want to reuse. For example, we can “automate” the compression of the build using this file:

# common options for jlink
--compress 2
--strip-debug
--no-header-files

Assuming the options file’s name is ‘options’, the original command now looks like this:

jlink
    --module-path target
    --add-modules com.hello.world
    --output target/output
    --launcher helloworld=com.hello.world/com.hello.world.Main
    @options

Using these options and jdk11, I was able to build a demo standalone desktop application (including the java.base and java.desktop modules) which weighs in at just about 38MB. Using 7-Zip to compress the image even further produces a 22MB archive. Not bad at all.

This newish way of deploying applications to the end user may help relieve some of the major pain points around Java and it’s use in the wild.