Crafting Lean Java Runtimes with jlink: SpringBoot Docker
Tags: java, springboot, docker, container
Published: 2023-11-26 16:14:14


In my last adventure into the realm of Spring Boot and Docker, we explored the art of [creating compact images using a JRE base](https://dev.to/alifrul/spring-boot-docker-images-using-jre-base-3jif). The journey was enlightening, but the quest for minimalism in Docker images never truly ends. So, let's embark on a new escapade, this time armed with the mighty _jlink_ command.

### Why jlink?

We love our Java applications, but we're not fans of oversized Docker images. [`jlink`](https://docs.oracle.com/en/java/javase/17/docs/specs/man/jlink.html) steps in to shrink the footprint by creating a runtime that contains only the modules required for our app. Smaller images mean faster downloads, quicker startups, and happier users.

Before we can use `jlink`, we need to determine which JDK modules are required by the application. We need `Jdeps`, a Java dependency analysis tool to [list all the module dependencies](https://mbien.dev/blog/entry/custom-java-runtimes-with-jlink).
To know more about how to use `jdeps` with the Spring Boot project you can look at this [article](https://dev.to/snyk/using-jlink-to-create-smaller-docker-images-for-your-spring-boot-java-application-3m99).


### Run JLink to create a custom Java Runtime
We added the `jlink` execution at the build stage. Later in the final stage, will copy the output into the final image.

```
RUN jlink -v \
    --add-modules java.base,java.logging,java.xml,jdk.unsupported,java.sql,java.naming,java.desktop,java.management,java.security.jgss,java.instrument \
    --strip-debug \
    --compress 2 \
    --no-header-files \
    --no-man-pages \
    --vendor-version="i made this" \
    --output /customjre
```

Arguments:

>* `add-modules` Adds the modules, to the default set of root modules. The module's name was generated by `jdeps`.
* `strip-debug` will [remove debugging information](https://medium.com/@david.delabassee/jlink-stripping-out-native-and-java-debug-information-507e7b587dd7) from the output runtime image.
* `compress=0|1|2` Enable compression of resources: 0= no compression, 1= string deduplication, 2= zip-compressed. [This might influence startup time slightly](https://mbien.dev/blog/entry/custom-java-runtimes-with-jlink).
* `vendor-version="I made this"` will appear on the second line of the output of `java -version`
* `no-header-files` Excludes header files.
* `no-man-pages` Excludes man pages.
* `output` Specifies the location of the generated runtime image.

After creating the custom JRE, we copy it into the JAVA_HOME of the final stage. The full dockerfile can be found [here](https://github.com/alifruliarso/spring-boot-online-survey/blob/main/jlink.Dockerfile).

```
FROM alpine:3.18.4
ENV JAVA_HOME /user/java/jdk21
ENV PATH ${JAVA_HOME}/bin:$PATH
COPY --from=java-build /customjre ${JAVA_HOME}
```

After rebuilding the docker image, here is the final result:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x1osh9cfj47t9fmpagl4.png)

We reduced the image size from 232MB to 98 MB. Finally, we have a smaller docker image of the Spring Boot application using Alpine Linux and Open JDK21.