Creating an image file using jlink - a basic tutorial
by Timmy Jose
Let’s explore how to create a minimal self-hosted image using the module system of Java and the jlink
tool.
First, for reference, I am using OpenJDK 11:
openjdk version "11.0.6" 2020-01-14
OpenJDK Runtime Environment (build 11.0.6+10)
OpenJDK 64-Bit Server VM (build 11.0.6+10, mixed mode)
Let’s create a directory for our little demo:
~/dev/playground$ mkdir helloworld_jlink
~/dev/playground$ cd helloworld_jlink/
~/dev/playground/helloworld_jlink$ tree .
0 directories, 0 files
Now, let’s create the module for our demo:
~/dev/playground/helloworld_jlink$ mkdir com.foo.bar
~/dev/playground/helloworld_jlink$ vim com.foo.bar/module-info.java
~/dev/playground/helloworld_jlink$ tree
.
└── com.foo.bar
└── module-info.java
1 directory, 1 file
~/dev/playground/helloworld_jlink$ cat com.foo.bar/module-info.java
module com.foo.bar {
}
Here, we have com.foo.bar
as the name of our new module. The module-info.java
file inside the com.foo.bar
directory simply makes its parent directory a module. Okay so far?
Now, let’s create our helloWorld
class inside the package com.foo.bar
(don’t be confused - this com.foo.bar
is a package inside the com.foo.bar
module):
~/dev/playground/helloworld_jlink$ cd com.foo.bar/
~/dev/playground/helloworld_jlink/com.foo.bar$ mkdir -p com/foo/bar
~/dev/playground/helloworld_jlink/com.foo.bar$ vim com/foo/bar/HelloWorld.java
~/dev/playground/helloworld_jlink/com.foo.bar$ cat com/foo/bar/HelloWorld.java
package com.foo.bar;
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hola, Mundo!");
}
}
At this point, our directory structure looks like so:
~/dev/playground/helloworld_jlink/com.foo.bar$ cd ..
~/dev/playground/helloworld_jlink$ tree
.
└── com.foo.bar
├── com
│ └── foo
│ └── bar
│ └── HelloWorld.java
└── module-info.java
4 directories, 2 files
Excellent. Now, let’s compile our class and generate a JAR file (note that jlink works with JAR files or a specific format called the JMOD format, which is quite useful in including native code and other non-Java resources. For this example, let’s stick to a JAR file):
By convention, let’s keep our compiled classes in a directory called mods
within which we also create a directory for the module name (since we might have more than one module in a project), so we create it first:
~/dev/playground/helloworld_jlink$ mkdir -p mods/com.foo.bar
~/dev/playground/helloworld_jlink$ tree
.
├── com.foo.bar
│ ├── com
│ │ └── foo
│ │ └── bar
│ │ └── HelloWorld.java
│ └── module-info.java
└── mods
└── com.foo.bar
6 directories, 2 files
Now we compile our classes to the correct destination:
~/dev/playground/helloworld_jlink$ javac -d mods/com.foo.bar/ com.foo.bar/module-info.java \
> com.foo.bar/com/foo/bar/HelloWorld.java
~/dev/playground/helloworld_jlink$ tree
.
├── com.foo.bar
│ ├── com
│ │ └── foo
│ │ └── bar
│ │ └── HelloWorld.java
│ └── module-info.java
└── mods
└── com.foo.bar
├── com
│ └── foo
│ └── bar
│ └── HelloWorld.class
└── module-info.class
9 directories, 4 files
Let’s create the JAR file. Again, by convention, the JAR file is stored in a directory called mlib
, so we create that first, and then generate the JAR file there:
~/dev/playground/helloworld_jlink$ mkdir mlib
~/dev/playground/helloworld_jlink$ tree
.
├── com.foo.bar
│ ├── com
│ │ └── foo
│ │ └── bar
│ │ └── HelloWorld.java
│ └── module-info.java
├── mlib
└── mods
└── com.foo.bar
├── com
│ └── foo
│ └── bar
│ └── HelloWorld.class
└── module-info.class
10 directories, 4 files
~/dev/playground/helloworld_jlink$ jar --create --file=mlib/com.foo.bar.hello.jar --module-version=0.0.1 --main-class=com.foo.bar.HelloWorld -C mods/com.foo.bar/ .
~/dev/playground/helloworld_jlink$ tree
.
├── com.foo.bar
│ ├── com
│ │ └── foo
│ │ └── bar
│ │ └── HelloWorld.java
│ └── module-info.java
├── mlib
│ └── com.foo.bar.hello.jar
└── mods
└── com.foo.bar
├── com
│ └── foo
│ └── bar
│ └── HelloWorld.class
└── module-info.class
10 directories, 5 files
Excellent. We can, at this point, just test the JAR file to make sure that it’s working:
~/dev/playground/helloworld_jlink$ java --module-path=mlib --module=com.foo.bar
Hola, Mundo!
~/dev/playground/helloworld_jlink$ java --module-path=mlib --describe-module com.foo.bar
com.foo.bar@0.0.1 file:///Users/z0ltan/dev/playground/helloworld_jlink/mlib/com.foo.bar.hello.jar
requires java.base mandated
contains com.foo.bar
Nice! We can also see that the module version that we specified while creating the “modular” JAR file has been applied correctly, and also that the dependencies for the module (basically only the core Java module, java.base, by default) is also listed correctly.
Now, all that remains to be done is to bundle the JAR file into a self-contained image:
~/dev/playground/helloworld_jlink$ jlink --module-path=$JAVA_HOME/jmods:mlib --add-modules=com.foo.bar --output=hello_app
~/dev/playground/helloworld_jlink$ ls
com.foo.bar hello_app mlib mods
You can replace $JAVA_HOME
with the correct path (in case this environment variable is not set correctly). Also note that I am on a mac, so I use : and the classpath
separator. Use ;
if you’re on Windows, for instance. The command is rather self-explanatory. Just note that hello_app
is the name of the image that we wish to generate on the fly. As the ls command shows, the app has been created successfully!
~/dev/playground/helloworld_jlink$ cd hello_app/
~/dev/playground/helloworld_jlink/hello_app$ du -h .
64K ./bin
4.0K ./include/darwin
192K ./include
72K ./lib/jli
352K ./lib/security
13M ./lib/server
37M ./lib
72K ./legal/java.base
72K ./legal
8.0K ./conf/security/policy/unlimited
12K ./conf/security/policy/limited
24K ./conf/security/policy
80K ./conf/security
88K ./conf
37M .
Not bad! A 37MB image. Note that you can compress it even more by passing flags to the jlink
command (such as --strip-debug
, for example). If you are on macOS/Linux/Unix, you can explore more options with man jlink
.
Finally, let’s run our image!
~/dev/playground/helloworld_jlink/hello_app$ bin/java --module com.foo.bar
Hola, Mundo!
Nooiiiiice! Well, how do we know for sure that only the bare minimum modules have been added? Simple enough - just list the modules that this JRE contains:
~/dev/playground/helloworld_jlink/hello_app$ bin/java --list-modules
com.foo.bar@0.0.1
java.base@11.0.6
And that’s it!