Core Java

Project Leyden’s AOT Code Cache: How Java Is Solving Its Cold-Start Problem Without GraalVM

Leyden delivered its first three features in Java 24 and 25. A fourth lands in JDK 26. Here’s why this matters — and how it’s fundamentally different from GraalVM’s native image approach.

If you’ve ever deployed a Java service inside a container, you know the feeling. The pod spins up, Kubernetes marks it ready, and for the next five to thirty seconds — depending on your framework — it’s barely functional. Requests crawl, timeouts fire, the JIT hasn’t warmed up yet. Your p99 latency is embarrassing. That’s the JVM cold-start problem, and for a long time, the only real answer was GraalVM Native Image. That’s changing now, quietly and incrementally, through Project Leyden.

Leyden isn’t a single feature or a big bang release. Instead, it’s a research project inside OpenJDK that is shipping one JEP at a time — starting with Java 24, continuing through Java 25, and picking up again with JDK 26. Together, these JEPs build an ahead-of-time (AOT) cache that dramatically reduces both startup time and time-to-peak-performance. And unlike GraalVM Native Image, none of it requires you to give up the full JVM or rewrite a line of code.

1. Why Cold-Start Is Such a Hard Problem

To understand what Leyden is doing, it helps to understand what the JVM is doing on every single startup — because it’s a lot more than most developers realise.

When your application starts, the JVM has to read, parse, verify, load, and link every class your application uses. A typical Spring Boot application touches somewhere between 15,000 and 25,000 classes by the time it’s fully initialised. Each of those classes goes through multiple stages of processing before any of its code can actually run. Additionally, the JIT compiler — HotSpot — doesn’t compile methods to native code until it has seen them run enough times to bother. That observation phase takes time. The result is a two-headed problem: startup latency (the time before the first request is handled) and warmup latency (the time before the JIT has generated optimised native code for the hot paths).

Furthermore, the traditional solution — Class Data Sharing (CDS) — helps with parsing and reading, but it stops short of loading and linking. That gap is exactly what Leyden is closing, one JEP at a time.

Where JVM Startup Time Goes

Approximate breakdown of a Spring Boot application startup (Java 21, no AOT cache). Based on profiling data from OpenJDK and Spring engineering benchmarks.

2. The Four JEPs — A Delivery Timeline

Project Leyden has shipped four JEPs across three JDK releases. Each one builds on the last, and together they tell a coherent story about shifting work earlier — from the production run to a one-time training run.

2.1 JEP 483 — Ahead-of-Time Class Loading & Linking (JDK 24)

This was the first Leyden feature to land in mainline OpenJDK. JEP 483 extends Class Data Sharing to go beyond parsing. After a training run, the JVM stores classes in a fully loaded and linked state inside an .aot cache file. On subsequent starts, those classes are available immediately — no re-parsing, no re-verification, no re-linking. The result: Spring PetClinic starts 41% faster, with roughly 21,000 classes appearing already linked at application boot. No code changes required. Furthermore, no new constraints are introduced.

2.2 JEP 514 — Ahead-of-Time Command-Line Ergonomics (JDK 25)

JEP 514 is the quality-of-life release. JDK 24’s workflow required three separate commands: record, assemble, run. JEP 514 collapses the first two into one. Pass -XX:AOTCacheOutput=app.aot and the JVM both records the training observations and builds the cache on shutdown. Consequently, the workflow drops to two steps: train once, deploy with the cache. It also introduced a new JDK_AOT_VM_OPTIONS environment variable for fine-tuning the cache creation sub-process without polluting the production command line.

2.3 JEP 515 — Ahead-of-Time Method Profiling (JDK 25)

Where JEP 483 addressed startup, JEP 515 attacks warmup. During a training run, the JVM records which methods are called most frequently. Those profiling records are stored in the AOT cache. In production, the JIT compiler reads them immediately at boot, rather than waiting to collect its own profiles. As a result, HotSpot can begin compiling hot methods to native code far earlier. In practice, this means reaching peak throughput significantly faster than a standard JVM — without giving up the JIT’s ability to adapt at runtime.

2.4 JEP 516 — Ahead-of-Time Object Caching with Any GC (JDK 26)

The most technically sophisticated of the four, JEP 516 addresses a real architectural constraint that had been quietly limiting the previous features: the AOT cache stored Java objects (like loaded Class instances and their associated strings and arrays) in a GC-specific binary format, memory-mapped directly into the heap at startup. That approach is fast — but it requires the object layout to match the GC’s exact memory model. ZGC, which uses colored pointers to encode metadata directly into object references, uses a fundamentally incompatible layout. So all of the previous Leyden features simply didn’t work with ZGC at all.

JEP 516 replaces the format with a GC-agnostic streaming approach. Objects are materialised by a background thread at startup, one by one, using the Access API — meaning the GC can lay them out according to its own rules. As long as a spare CPU core is available, this background work doesn’t slow the startup process in practice. Additionally, the JDK now ships with a baseline AOT cache that works across all GC implementations, giving every Java application a free starting benefit even without a custom training run.

Startup Time Improvements Across Leyden JEPs

Relative improvement over baseline JVM (no cache) for Spring PetClinic–scale applications. Data from OpenJDK Project Leyden benchmarks and inside.java.

3. Using the AOT Cache in Practice

The workflow in JDK 25 and beyond is genuinely simple. You train once — ideally mimicking production as closely as possible — and deploy with the cache from then on. Here’s what that looks like end to end:

AOT cache workflow (JDK 25+)

# Step 1 — Training run: record observations + write the cache on shutdown
java -XX:AOTCacheOutput=app.aot \
     -jar myapp.jar

# Step 2 — Production run: load the cache on every subsequent start
java -XX:AOTCache=app.aot \
     -jar myapp.jar

Training run fidelity mattersThe training run should closely mirror production behaviour. The JVM will refuse to use a cache if the classpath, JVM flags, or key runtime conditions differ significantly from when it was built. Mock external dependencies to load the right classes, but avoid loading test-only code that won’t appear in production.

One important constraint to keep in mind: Leyden currently only caches classes loaded by the standard JDK class loaders. Applications that rely heavily on custom class loaders — a common pattern in some older OSGi or plugin architectures — won’t get full benefit. Frameworks like Quarkus have addressed this by introducing a dedicated aot-jar packaging that delegates all class loading to the standard loader transparently.

4. Leyden vs. GraalVM Native Image — The Real Difference

This is where the comparison gets interesting, because it’s tempting to think of these as two solutions to the same problem competing for the same use case. They’re not — not exactly.

GraalVM Native Image performs closed-world AOT compilation. It analyses your entire application at build time, compiles everything to native machine code, and produces a self-contained executable. The result is genuinely impressive: near-instant startup (often under 100ms), very low memory footprint, and a small container image. However, the closed-world assumption means that dynamic features — reflection, dynamic class loading, runtime proxies, serialisation — require explicit configuration to work. The native image build itself can take minutes. And there is no JIT at runtime, so the peak throughput of a warmed-up JVM can exceed native image performance under sustained load.

Leyden, by contrast, stays within the open-world JVM model. Your application runs on a real HotSpot JVM, with full dynamic capabilities intact. The AOT cache simply front-loads a portion of the work that would otherwise happen at startup, and seeds the JIT with prior observations. The JIT is still running. Dynamic class loading still works. Reflection still works — no configuration file needed. The tradeoff is that Leyden’s startup improvements, while substantial, won’t match native image on raw startup latency. The Spring PetClinic’s 41% improvement is impressive — but native image can achieve 90%+ reductions in startup time for the same app.

“Native image optimises for startup and footprint. Leyden optimises for the gap between ‘nothing works yet’ and ‘everything works well’ — without giving up the JVM you already know.”— Synthesis of Project Leyden goals, openjdk.org/projects/leyden

The honest answer is that both approaches will coexist, serving different deployment profiles. Short-lived functions and CLI tools are excellent native image candidates. Long-running services with complex dynamic behaviour — the majority of enterprise Java — are where Leyden shines, because it delivers meaningful improvement with zero code changes and zero compatibility risk.

DimensionGraalVM Native ImageProject Leyden (AOT Cache)
Code changes requiredSometimes (reflection config)None
Full JVM featuresLimited (closed-world)Yes (open-world)
JIT compiler at runtimeNoYes
Peak throughput ceilingBelow warmed JVMFull JVM peak
Startup improvementVery high (80–95%)Significant (40–55%+)
Build time costHigh (minutes)Low (one training run)
Container image sizeVery smallJDK + cache file (~40–200 MB)
ZGC compatibleN/A (no GC)Yes (from JDK 26)
Part of OpenJDKNo (separate distribution)Yes

5. What This Means for Containers and Serverless

The container and serverless use case is where Leyden’s design decisions feel most deliberate. In a horizontally scaled deployment, you build the AOT cache once — during your CI pipeline — and ship it as part of your container image. Every pod that spins up loads the same cache file. The training overhead is paid once; every deployment run benefits.

Moreover, because the cache is tied to the classpath and JVM flags, it integrates naturally with Docker and OCI build pipelines. A typical Dockerfile adds one layer for the training run, writes the .aot file, and the final image includes both the jar and the cache. The net result is that your Kubernetes pods reach a useful state significantly faster — with no changes to your application code, your framework configuration, or your deployment tooling.

Framework support in 2025–2026Spring Boot 3.3+ supports Leyden AOT caching out of the box. Quarkus has built dedicated aot-jar packaging. Micronaut support is also in progress. If you’re on a modern version of any major Java framework, you’re likely already in a position to benefit with minimal effort.

For serverless specifically, the picture is more nuanced. AWS Lambda and similar platforms cold-start every function invocation after a period of inactivity. Leyden’s cache file would need to be bundled into the deployment package and persist across invocations — which is possible, but requires the execution environment to support it. In practice, CRaC (Coordinated Restore at Checkpoint) remains a stronger fit for serverless, since it restores from a full memory snapshot. That said, Leyden’s broader compatibility story — working on any OS, any GC, without security-sensitive memory snapshots — makes it a more attractive default for teams not specifically chasing sub-100ms startup.

6. What Comes Next for Leyden

The roadmap is genuinely exciting. The Leyden premain branch — the experimental prototype that runs ahead of what’s in mainline — currently includes two features not yet in any released JDK: Ahead-of-Time Code Compilation, which stores pre-compiled native method code in the cache (so methods can execute natively from the very first startup with no JIT delay at all), and Ahead-of-Time Dynamic Proxy Generation, which pre-generates the reflective proxies that frameworks like Spring use extensively. Both features are enabled by default when building a cache from the premain branch and show further meaningful startup reductions in early benchmarks.

Furthermore, as the JDK now ships with a baseline AOT cache covering JDK classes — thanks to JEP 516 — even applications that skip the custom training run will see a small baseline improvement from JDK 26 onwards. That’s a significant quality-of-life win for the ecosystem as a whole.

7. What We’ve Learned

Project Leyden is a methodical, backwards-compatible answer to Java’s cold-start problem — delivered as a series of JEPs that each shift a specific category of work from production runtime to a one-time training run. JEP 483 (JDK 24) moved class loading and linking. JEP 514 and JEP 515 (JDK 25) simplified the workflow and added method profiling for faster JIT warmup. JEP 516 (JDK 26) made the cache GC-agnostic, unlocking ZGC support and shipping a baseline cache with the JDK itself.

Unlike GraalVM Native Image, Leyden does not require code changes, closed-world analysis, or sacrificing the JIT. The startup gains (~41% for Spring PetClinic) are meaningful for container deployments, and the warmup gains are significant for any service that cares about p99 latency during scale-out. The comparison isn’t “Leyden vs. Native Image” — it’s “Leyden for JVM workloads, Native Image for startup-critical, footprint-sensitive deployments.” Both will matter.

If you’re running JDK 25 today, you can start experimenting with AOT caching in two commands. If you’re on JDK 26, you’re already benefiting from the baseline cache on every startup. The cold-start problem isn’t solved yet — but it’s being systematically dismantled.

Key Links

Eleftheria Drosopoulou

Eleftheria is an Experienced Business Analyst with a robust background in the computer software industry. Proficient in Computer Software Training, Digital Marketing, HTML Scripting, and Microsoft Office, they bring a wealth of technical skills to the table. Additionally, she has a love for writing articles on various tech subjects, showcasing a talent for translating complex concepts into accessible content.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Back to top button