DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • Retrieval Augmented Generation With Spring AI 2.0, Claude, and PGvector
  • Implementing Asynchronous Communication Between Microservices Using Kafka and Spring Boot
  • Smart Deployment Strategies for Modern Applications
  • How to Identify the Underlying Causes of Connection Timeout Errors for MongoDB With Java

Trending

  • Real-Time Supply Chain Event Streaming With Kafka and Neo4j
  • I Built a Java Version Manager by Fixing Other Tools' Open Bugs
  • How I Built a Star Wars Grogu Product Research Agent With Codex, Lark, and SerpApi
  • AI in SRE: A Practical Autonomy Model for Self-Healing Infrastructure
  1. DZone
  2. Software Design and Architecture
  3. Containers
  4. The Startup Time Trick Hiding Inside Your Docker Build

The Startup Time Trick Hiding Inside Your Docker Build

Spring Boot pods reload the same classes on every start. A CDS training run inside your Dockerfile caches that work once and cuts startup time roughly in half.

By 
Garima Agarwal user avatar
Garima Agarwal
·
Sep. 03, 26 · Analysis
Likes (0)
Comment
Save
Tweet
Share
259 Views

Join the DZone community and get the full member experience.

Join For Free

Every Java developer who runs services on Kubernetes has watched this scene play out. Traffic spikes, the autoscaler adds a pod, and then everyone waits. The container is running in two seconds. The application is not ready for another twelve seconds. During those ten seconds, your existing pods absorb the extra load, latency climbs, and if things are bad enough, the autoscaler panics and adds even more pods that are also not ready.

I spent years treating Spring Boot startup time as a fact of life, the way you treat weather. Then I found out the JVM has had a fix for a big chunk of it since Java 12; it works beautifully inside Docker, and almost nobody bakes it into their images. It is called Class Data Sharing, CDS for short, and this article shows you how to make your Docker build do the work

Where Those Twelve Seconds Actually Go

When a Spring Boot application starts, the JVM is not mostly running your code. It is loading classes. A plain REST service with Spring Web, Spring Data, and a driver or two loads somewhere between ten and twenty thousand classes before it serves its first request.

For every single one of those classes, the JVM does the same ritual. Find the class file inside a jar, read the bytes, parse them, verify the bytecode is legal, and build the internal metadata structures it needs at runtime. Thousands of times. Every startup. In every pod.

Here is the part that should bother you. Your container image never changes after you build it. The same jar, the same classes, the same parsing work, repeated identically in every pod that ever starts from that image. The JVM is solving the same puzzle again and again and throwing away the answer each time.

CDS is the JVM saying: let me solve it once, write the answer to a file, and just memory map that file next time.

What a CDS Archive Is

A CDS archive is a file, usually ending in .jsa, that contains classes already parsed and verified, stored in the exact internal format the JVM uses in memory. On startup, the JVM maps this file straight into memory. No finding, no parsing, no verifying. The work was done ahead of time.

You have been using CDS without knowing it. Modern JDKs ship with a default archive covering the core JDK classes, which is why java -version is fast. The step almost everyone skips is creating an archive for your application classes, all fifteen thousand of them. That is where the real win lives.

The mechanism has one rule that matters for us. The archive must be created with the same JVM and the same classpath that will use it. That rule sounds annoying until you realize a Docker image is the one place in your entire infrastructure where JVM and classpath are frozen forever. Docker is not just compatible with CDS. It is the perfect home for it.

The Training Run

Creating the archive takes two steps. First you do a training run, where the JVM starts your application, watches which classes get loaded, and writes the list down. Then you exit, and the JVM turns that list into the archive.

Since Java 13, this is pleasantly simple:

Shell
 
java -XX:ArchiveClassesAtExit=app.jsa -jar app.jar


Run the app, let it come up, stop it, and app.jsa appears. From then on you start the app like this: 

Shell
 
java -XX:SharedArchiveFile=app.jsa -jar app.jar


There is an obvious question here. The training run wants to actually start the application, and inside docker build there is no database, no message broker, nothing to connect to. A Spring Boot app that cannot reach Postgres will crash during training.

Spring Boot 3.3 solved this neatly. Setting one property makes the application run through its entire startup sequence, create all bean definitions, and then exit just before touching the outside world:

Shell
 
java -Dspring.context.exit=onRefresh -XX:ArchiveClassesAtExit=app.jsa -jar app.jar


The application loads nearly everything it will ever load, writes the archive, and exits cleanly with no infrastructure needed. This is exactly what a Docker build stage can do.

The Dockerfile

Here is the complete picture: a multi-stage build where the image trains itself:

Shell
 
FROM eclipse-temurin:21-jdk-alpine AS build
WORKDIR /build
COPY . .
RUN ./mvnw -B package -DskipTests

# Explode the jar so the classpath is stable
RUN java -Djarmode=tools -jar target/app.jar extract --destination /app

FROM eclipse-temurin:21-jre-alpine AS runtime
WORKDIR /app
COPY --from=build /app /app

# Training run: start the context, record classes, exit
RUN java -Dspring.context.exit=onRefresh \
    -XX:ArchiveClassesAtExit=/app/app.jsa \
    -jar /app/app.jar

ENV JAVA_TOOL_OPTIONS="-XX:SharedArchiveFile=/app/app.jsa"
ENTRYPOINT ["java", "-jar", "/app/app.jar"]


Two details in there deserve a closer look.

The extract step unpacks the fat jar into a folder with the dependencies laid out as plain files. CDS is picky about the classpath being identical between training and real runs, and a fat jar with nested jars inside it makes that fragile. The exploded layout keeps the classpath boring and stable, which is exactly what CDS wants. On Spring Boot 3.2 and older, the same idea works through the layertools jarmode instead.

The training run happens as a RUN instruction, which means it executes once at build time on your CI server. Every container that ever starts from this image inherits the archive for free. You did the class loading homework once, in the build, and ten thousand pod starts copy the answer.

What You Get

Numbers vary with how heavy your application is, but the pattern is consistent. A typical Spring Boot 3 web service that started in 10 to 12 seconds lands somewhere between 5 and 7. The JVM portion of startup shrinks dramatically, and as a bonus, the archive is memory-mapped and shared, so if you run several JVMs on one node, they share those pages and total memory drops too.

You can verify the archive is actually being used, which I recommend, because CDS fails silently by design. If something mismatches, it just quietly falls back to normal class loading:

Shell
 
docker run --rm my-service -Xlog:class+load=info | head -5


Classes loaded from the archive say source: shared objects file. If you see jar paths instead, the archive is being ignored, and the log will usually tell you why. The usual culprit is a classpath that differs from training, even by one entry.

One honest caveat. The training run exercises startup, not your traffic. Classes that only load when a specific endpoint gets hit for the first time are not in the archive, so those first requests still do normal loading. The archive covers the framework and wiring, which is most of the cost, but it is not a magic warm-up for everything.

Why This Beats the Alternatives You Have Heard Of

Whenever container startup time comes up, someone mentions GraalVM native images, and native images are impressive. Millisecond startup is real. But they come with a price list: long build times, a closed-world assumption that fights with reflection, some libraries that simply do not work, and a different runtime profile you have to learn to debug.

CDS costs you five lines of Dockerfile. Your application is still a completely normal JVM application. Same debugging, same profilers, same libraries, same behavior, just faster out of the gate. For most teams, that trade-off is not even close.

It also stacks with what is coming. Project Leyden's AOT cache in Java 24 and beyond is essentially this same idea grown up, caching not just parsed classes but resolved linkage and compiled code. The Dockerfile pattern you build today, a training run at build time producing a cache file shipped in the image, is exactly the shape Leyden uses. Learning it now means the future is a flag change.

The Takeaway

Your Docker image is immutable. Your JVM does expensive, perfectly repeatable work on every startup. Those two facts fit together like puzzle pieces, and a training run inside docker build is where they connect. One extra build step, and every pod your autoscaler ever creates comes up in half the time.

The next time you watch a rollout crawl because pods take forever to go ready, remember that the answer was hiding inside the build all along.

Java virtual machine Docker (software) Spring Boot

Opinions expressed by DZone contributors are their own.

Related

  • Retrieval Augmented Generation With Spring AI 2.0, Claude, and PGvector
  • Implementing Asynchronous Communication Between Microservices Using Kafka and Spring Boot
  • Smart Deployment Strategies for Modern Applications
  • How to Identify the Underlying Causes of Connection Timeout Errors for MongoDB With Java

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook