Skip to main content

Command Palette

Search for a command to run...

Creating jlink'd application images using Gradle

Updated
4 min read
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.

💡
Module is a generic and hence overloaded term in the Java ecosystem. The Java Platform Module System defines modules. There are Gradle/Maven build modules. And the Spring Modulith project newly defines packages as modules.

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.

💡
Please refer to the initial setup instructions from one of the initial posts.

Step 1: Clone the sample Helidon project

git clone https://github.com/pushkarnk/helidon-hello && \
cd helidon-hello

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'
     ]
}
💡
The options are options to be passed to the jlink tool. See jlink —help with your JDK installation.
💡
The 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
💡
The size of this image is 76.1 MB. Helidon does not have support for fat/uber jars; it is not straightforward to create an image with a fat-jar to make size comparisons.
💡
We could make a size comparison against a container image containing the java runtime, thin-jar and dependency jars. I leave that as an exercise!

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!

Java application containers on Ubuntu

Part 3 of 6

The Maven and Gradle plugins for Rockcraft let you build Ubuntu container images for your Java application through a maven goal or a gradle task. They let you build and test apps in the very same container they would be eventually deployed in.

Up next

Creating container images with self-contained Java application images

At the end of the previous post, I promised to talk about container image size reduction by packaging only those Java modules that the application actually depends on. For this we would need a modular Java application that uses the Java Platform Modu...

More from this blog

Java for Ubuntu

14 posts