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.