Skip to main content
  1. Posts/

Creating Lightweight Java Container with Custom JRE

·541 words

Java applications, often packaged in large container images, can benefit significantly from optimization techniques that reduce their footprint. This is crucial for efficient resource utilization, especially in cloud environments. In this post, we explore how to use jdeps and jlink, tools included with Amazon Corretto’s OpenJDK, to analyze dependencies and create a minimal custom Java Runtime Environment (JRE) that can substantially reduce the size of Docker images for Java applications.

Initial Docker Setup #

A typical Docker setup for a Spring Boot application might start with a basic Dockerfile like this:

FROM amazoncorretto:11.0.20-alpine

COPY ./sample-app.jar /app/sample-app.jar

ENTRYPOINT ["/jre/bin/java", "-jar", "/app/app.jar"]

This Dockerfile sets up a Java environment using Amazon Corretto’s Alpine-based image. It copies the application JAR into the image and sets the working directory. The application is exposed on port 8000.

jdeps is a Java Dependency Analysis Tool that helps determine the specific modules required by a Java application. jlink is a Java Linker that can create a custom JRE based on the modules needed. The combined use of these tools can significantly reduce the Java runtime’s size.

Here’s a basic example of how jdeps can be used:

jdeps --ignore-missing-deps --print-module-deps -q --recursive --multi-release 17 --class-path="./*" --module-path="./*" ./sample-app.jar

The output might look something like this:


With these modules identified, jlink can create a stripped-down version of the JRE:

jlink \
    --verbose \
    --add-modules java.base, \
    --strip-debug \
    --no-man-pages \
    --no-header-files \
    --compress=2 \
    --output customjre

Building the Optimized Docker Image #

Using the above insights, we can construct a multi-stage Dockerfile that builds a minimal custom JRE and then creates a lightweight container for the application:

# STAGE 1: Dependency analysis
FROM amazoncorretto:17.0.2 AS deps
COPY ./sample-app.jar /root/sample-app.jar
RUN $JAVA_HOME/bin/jdeps \
    --ignore-missing-deps \
    --print-module-deps \
    -q --recursive \
    --multi-release 17 \
    /root/sample-app.jar > /

# STAGE 2: JRE creation
FROM amazoncorretto:17.0.2 AS corretto-jdk
RUN yum install -y binutils # needed for jlink
COPY --from=deps / /
RUN $JAVA_HOME/bin/jlink \
    --verbose \
    --add-modules $(cat / \
    --strip-debug \
    --no-man-pages --no-header-files \
    --compress=2 \
    --output /root/customjre

# STAGE 3: Final image
FROM debian:bookworm-slim
COPY --from=corretto-jdk /root/customjre/ /jre/
COPY ./sample-app.jar /app/
CMD ["java", "-jar", "sample-app.jar"]

Results and Benefits #

By using a custom JRE, we can reduce the container size dramatically. In one of our tests, the image size was reduced by over 60%, dropping from 563MB to just 225MB.

container w/ corretto:17.0.2container w/ customjrerate
Size563MB225MBdecreased 60.06%

Troubleshooting #

These are some issues that I had while going thorugh above steps.

Issue 1: “*/jre/bin/java no such file or directory” with Alpine Images

  • Alpine images use musl libc instead of glibc, which can cause issues with binaries expecting glibc. Switching to a Debian-based image or installing glibc in Alpine can resolve these problems.

Issue 2: Docker Image Size Discrepancies

  • Docker images may appear smaller on registries like Docker Hub because layers are compressed. Actual sizes may vary when pulled locally.

Conclusion #

Optimizing Java container images with jdeps and jlink is an effective way to reduce their size and improve deployment efficiency. This approach is particularly beneficial for environments where resource constraints are significant, such as Kubernetes clusters running multiple microservices.

Reference #