Cloud Native 4 min read

How to Optimize Spring Boot Docker Images with Layered JARs and Multi‑Stage Builds

This article explains why a plain Dockerfile for a Spring Boot fat JAR is inefficient, introduces Spring Boot's layered JAR format, and provides a multi‑stage Dockerfile that leverages layer caching to speed up builds and reduce runtime overhead.

Eric Tech Circle
Eric Tech Circle
Eric Tech Circle
How to Optimize Spring Boot Docker Images with Layered JARs and Multi‑Stage Builds

Why a Simple Dockerfile Is Problematic

Building a Docker image directly from a Spring Boot fat JAR adds unnecessary runtime overhead and makes rebuilds slow because the JAR and its dependencies are packed into a single layer. Each code change forces the entire layer to be rebuilt, preventing Docker cache reuse.

Spring Boot Layered JAR

Since Spring Boot 2.3, the Maven/Gradle plugins produce JAR files that contain explicit layer metadata.

The default layers are:

dependencies : regular compile‑time dependencies.

spring-boot-loader : classes under org/springframework/boot/loader.

snapshot-dependencies : snapshot versions of dependencies.

application : the application’s own classes and resources.

When the JAR is built, a layers.idx file records the order of these layers: dependencies, spring-boot-loader, snapshot-dependencies, application.

Inspecting Layers with Layertools

You can extract the layers locally to see their contents:

java -Djarmode=layertools -jar application.jar extract

Recommended Multi‑Stage Dockerfile

The following Dockerfile separates the extraction of each layer into its own build stage, allowing Docker to cache unchanged layers:

FROM eclipse-temurin:21-jre AS builder
ARG JAR_FILE=target/application.jar
COPY ${JAR_FILE} app.jar
# Extract layers
RUN java -Djarmode=layertools -jar app.jar extract

FROM eclipse-temurin:21-jre
COPY --from=builder /layers/dependencies/ /app/dependencies/
COPY --from=builder /layers/spring-boot-loader/ /app/spring-boot-loader/
COPY --from=builder /layers/snapshot-dependencies/ /app/snapshot-dependencies/
COPY --from=builder /layers/application/ /app/application/
EXPOSE 8080
ENTRYPOINT ["java","-cp","/app/*","com.example.Main"]

Build the image with:

docker build --build-arg JAR_FILE=target/application.jar .

Each COPY corresponds to a layer extracted by layertools, so modifying only the application code invalidates the application layer while reusing the cached dependency layers.

Observing Cache Behaviour

After changing source code, rebuild the image and watch Docker reuse the cached layers for dependencies and the loader, dramatically reducing build time.

Reference

Container Images :: Spring Boot – https://docs.spring.io/spring-boot/reference/packaging/container-images/index.html

backendcloud-nativeDockerSpring Bootmulti-stage-buildlayered-jar
Eric Tech Circle
Written by

Eric Tech Circle

Backend team lead & architect with 10+ years experience, full‑stack engineer, sharing insights and solo development practice.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.