Spring Boot starts slower than its developers would like, mainly because the JVM has to find, load, verify and link thousands of classes — and Spring then has to build the application context — every single time. Project Leyden is the OpenJDK effort to shift some of that work to a training run ahead of time, and its first results are already in the JDK you probably run: the AOT cache.

No code changes, no native-image build, no reflection configuration. You run your app once to record what it loads, and every later start reuses that work.

Think of it like this
Imagine a chef who, every morning, reads every recipe book and lays out every ingredient before cooking breakfast. The AOT cache is a prep list written the first morning — next time, the ingredients are already laid out, so cooking starts almost immediately.

What landed in which JDK

JDK 24 introduced ahead-of-time class loading and linking (JEP 483): classes are loaded and linked during a training run and stored in a cache. JDK 25 — the current LTS — made it much simpler to use, creating the cache in a single training run with one flag (JEP 514), and added method profiles to the cache (JEP 515) so the JIT compiler warms up faster. JEP 483's own measurements showed the Spring PetClinic sample starting roughly 40% faster.

Using it with Spring Boot

The AOT cache needs a plain classpath of jars, not Spring Boot's nested "fat" jar. So step one is to extract the jar, which Spring Boot supports out of the box. Then do a training run and point future runs at the cache:

terminal
# 1. Extract the executable jar into a classpath-friendly layout java -Djarmode=tools -jar loan-api.jar extract --destination app # 2. Training run: start the context, record, exit, write the cache (JDK 25+) cd app java -XX:AOTCacheOutput=app.aot -Dspring.context.exit=onRefresh -jar loan-api.jar # 3. Every normal start reuses the cache java -XX:AOTCache=app.aot -jar loan-api.jar

-Dspring.context.exit=onRefresh makes Spring Boot start the context and exit as soon as it has refreshed — a convenient, repeatable training run. A training run that also exercises real requests gives the JIT richer profiles, at the cost of a more involved build.

Baking it into a container image

The cache is only valid with the same JDK build and the same classpath it was created with. The simplest way to guarantee that is to create it inside the image you ship:

Dockerfile
FROM eclipse-temurin:25-jre AS build WORKDIR /build COPY target/loan-api.jar . RUN java -Djarmode=tools -jar loan-api.jar extract --destination app WORKDIR /build/app RUN java -XX:AOTCacheOutput=app.aot -Dspring.context.exit=onRefresh -jar loan-api.jar FROM eclipse-temurin:25-jre WORKDIR /app COPY --from=build /build/app/ ./ ENTRYPOINT ["java", "-XX:AOTCache=app.aot", "-jar", "loan-api.jar"]

Both stages use the same base image, so the cache matches the runtime. If the cache doesn't match — a different JDK build, a changed jar — the JVM ignores it and starts normally, so a stale cache costs you speed, not correctness.

Gotchas worth knowing
The training run starts your Spring context, so it may try to reach a database, message broker or config server. Use a build profile that disables those connections, or create the cache in CI with Testcontainers. Rebuild the cache on every release — it's tied to the exact jars. Measure: compare time-to-ready with and without the cache on your real service and hardware. How it compares: GraalVM native images start faster still but need AOT-compatible code and a heavier build; CRaC restores a full checkpoint of a running JVM but depends on runtime and platform support. The AOT cache is the lowest-effort win of the three.

On Kubernetes, a faster start also means shorter startup probes and faster scale-out — see the Spring Boot on Kubernetes checklist. On AWS Lambda, the Java 25 runtime already uses AOT caches; the Lambda cold-start guide covers the other options.

Frequently asked questions

What is the Java AOT cache?

A cache created during a training run that stores classes already loaded and linked — and, from JDK 25, method profiles — so later JVM starts skip that work. It comes from OpenJDK's Project Leyden and needs no code changes.

How do I create an AOT cache for a Spring Boot app?

Extract the jar with java -Djarmode=tools -jar app.jar extract, run it once with -XX:AOTCacheOutput=app.aot (optionally with -Dspring.context.exit=onRefresh), then start the application with -XX:AOTCache=app.aot.

Which Java version do I need for the AOT cache?

Ahead-of-time class loading and linking arrived in JDK 24. JDK 25 added the single-step -XX:AOTCacheOutput workflow and method profiling, so Java 25 LTS is the practical minimum.

The AOT cache (Project Leyden) speeds up startup with no code changes.
On Java 25, create it in one training run with -XX:AOTCacheOutput.
Spring Boot needs an extracted jar; spring.context.exit=onRefresh makes training easy.
Build the cache in the image you ship, and rebuild it every release.