Copy your Spring Boot jar into a builder stage, run it in jarmode tools to extract its layers, then copy each layer as its own Dockerfile step into a JRE based final stage. Dependencies change rarely and code changes often, so separate layers mean smaller rebuilds and pushes.
Why layers help security work too
Speed is the usual reason to split a Spring Boot fat jar. There is a second benefit: when dependencies sit in their own layer, a dependency update changes one layer and the application code layer stays identical. That makes it easier to see what a rebuild actually changed, and to reason about what a scanner will report against which layer.
The Spring Boot documentation explains that converting an uber jar into a Docker image takes a few lines, but using the layering feature results in an optimized image. When a jar contains the layers index file, the spring-boot-jarmode-tools jar is added as a dependency, which lets the application start in a special mode that runs something other than your app, for example extracting layers.
The steps
- Build the jar with your build tool.
- In a builder stage, copy the jar and extract it.
- In the final stage, copy the extracted directories in order, from least to most frequently changing.
- Run the application launcher class that the extraction produced, or the extracted application jar, as the Spring docs show.
FROM eclipse-temurin:21-jre AS builder
WORKDIR /builder
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} application.jar
RUN java -Djarmode=tools -jar application.jar extract --layers --destination extracted
FROM eclipse-temurin:21-jre
WORKDIR /application
COPY --from=builder /builder/extracted/dependencies/ ./
COPY --from=builder /builder/extracted/spring-boot-loader/ ./
COPY --from=builder /builder/extracted/snapshot-dependencies/ ./
COPY --from=builder /builder/extracted/application/ ./
USER 1000
ENTRYPOINT ["java", "-jar", "application.jar"]
This follows the structure in the Spring Boot reference. Check the current reference for the exact extract options for your Spring Boot version.
What each layer holds
| Layer directory | Content | Changes how often |
|---|---|---|
| dependencies | Released library jars | Only on dependency updates |
| spring-boot-loader | Launcher classes | Only on Spring Boot upgrades |
| snapshot-dependencies | Snapshot library jars | Often, if you use snapshots |
| application | Your classes and resources | Every commit |
The Spring documentation also notes that the layout from jarmode is friendly to AOT cache and class data sharing out of the box.
Layers do not replace scanning
Splitting layers changes where files live, not what they are. A vulnerable library stays vulnerable in its own layer. Scan the jar and the built image, and rebuild when a library advisory lands. Buildpacks are another route: the Spring docs note that the Paketo Spring Boot buildpack supports the same layers index, so layer customizations apply there too.
Try it yourself
Open the dependency inventory demo and look at how a dependency list groups direct and transitive libraries. The dependencies layer in your image is that list, packaged.
Click through it at your own pace, or open it full screen.
Where Safeguard fits
Safeguard builds an SBOM from your Maven or Gradle project and scans dependencies, with reachability analysis and fix pull requests that are off by default and under your control. A free scan is the quickest way to see your dependency layer's findings.
Quick check
Does the jarmode extract step need the full JDK?
It runs the jar with java, so a JRE is enough for the extract command shown in the Spring documentation. Confirm on your versions.
Should the last COPY always be the application layer?
Yes, put the fastest changing layer last, so earlier layers stay cached and unchanged.
Sources
Last reviewed: 19 October 2026.