Creating jlink'd application images using Gradle

The previous post showed how a self-contained runtime image for a non-modular application could be packaged into a container using the Badass Runtime plugin and the Rockcraft Gradle plugin.
Speaking of modules as defined by the Java Platform Module System (JPMS), the Spring Boot framework is non-modular. For this post I went in search of Java web frameworks that are modular (as per JPMS). And I found Helidon.
Background
The JPMS lets users write modular Java applications, with well defined dependency relationships, and create custom application runtime images that include the required system and application modules. The jlink tool is used to create such jlink’d images.
Custom jlink’d images only include the bare-minimum set of modules required by the application. We can further optimize their memory footprint by packaging them atop chiseled Ubuntu images, thus making them an excellent choice for cloud-native deployments.
This post demonstrates using the Badass Jlink Gradle plugin in conjunction with the Rockcraft Gradle plugin to produce such container images effortlessly for your Gradle-based Java applications.
Creating chiseled container images with custom runtime images
As mentioned earlier, I prefer to use Helidon over Spring Boot here; the former has full support for JPMS.
Step 1: Clone the sample Helidon project
git clone https://github.com/pushkarnk/helidon-hello && \
cd helidon-hello
Step 2: Add the Badass Jlink plugin and jlink options to the build.gradle
Add the Badass Jlink plugin. Make sure this is added before the Rockcraft Gradle plugin.
plugins {
id 'java'
id 'application'
id ('org.beryx.jlink') version '3.0.1'
}
Let us also configure the jlink options.
jlink {
mainClass = 'com.example.helidon.hello.Main'
launcher {
name = 'heliodon-hello'
}
options = [
'--strip-debug',
'--compress', '2',
'--no-header-files',
'--no-man-pages',
'--ignore-signing-information'
]
}
options are options to be passed to the jlink tool. See jlink —help with your JDK installation.launcher option lets us configure the name of the custom runtime launcher.Step 3: Add the Rockcraft Gradle plugin
Add the Rockcraft Gradle plugin. Make sure this is added after the Badass Jlink plugin.
plugins {
id 'java'
id 'application'
id ('org.beryx.jlink') version '3.0.1'
id ('io.github.rockcrafters.rockcraft') version '1.2.1'
}
Step 4: Build the chiseled application container image
Invoke the push-rock task. The jlink task is run as one of the dependency tasks.
./gradlew push-rock -i
If the above commands completes successfully, a docker image will be pushed to the local daemon.
$ docker image ls | more -3
REPOSITORY TAG IMAGE ID CREATED SIZE
helidon-hello 1.0.0 14d4f6b16815 About a minute ago 76.1MB
helidon-hello latest 14d4f6b16815 About a minute ago 76.1MB
Step 5: Launch the application container and test
Let’s inspect the contents of the container using pebble.You must be able to locate the custom launcher under the /image/bin directory.
$ docker run helidon-hello exec ls /image/bin
helidon-hello
helidon-hello.bat
java
keytool
Let’s launch the container invoking the custom launcher.
docker run --network=host helidon-hello:latest exec /image/bin/helidon-hello
The application should now be available at localhost and the reported ephemeral port number.
There is no Helidon logging implementation on classpath, skipping log configuration.
Aug 20, 2025 12:42:53 PM io.helidon.common.features.HelidonFeatures features
INFO: Helidon SE 4.1.4 features: [Config, Encoding, Media, WebServer]
Aug 20, 2025 12:42:53 PM io.helidon.webserver.ServerListener start
INFO: [0x28f843a4] http://0.0.0.0:45603 bound for socket '@default'
Aug 20, 2025 12:42:53 PM io.helidon.webserver.LoomServer startIt
INFO: Started all channels in 34 milliseconds. 340 milliseconds since JVM startup. Java 21.0.8+9-Ubuntu-0ubuntu125.04.1
Helidon Hello World with JPMS started!
Server running at: http://localhost:45603
The adoption question mark
When Java modularity and the Java Platform Module System was introduced, there were opinions that it was an ahead-of-time feature. Indeed, eight years after its introduction, the adoption is still a question mark. Many popular Java frameworks have consciously not adopted JPMS. The Spring project as an example. Instead of supporting JPMS, Spring and Spring Boot have introduced other features like the Sping Modulith and support for GraalVM native images.
For a certain category of applications, GraalVM native images are a promising alternative to custom, jlink’d runtime images. I plan to cover Rockcraft Gradle plugin’s support for GraalVM native images in the next post.
Here’s the screen-cast for the demo. Thanks for reading along!




