Project Leyden and AOT Caching: Is “Fast Startup” Actually Solved Now?
Java 26 just extended AOT caching to ZGC. Before you reach for GraalVM Native Image again, it’s worth asking what that actually changes.
For years, “Java starts too slowly for serverless” was simply accepted as fact. Then GraalVM Native Image showed up, compiled Java into a native binary, and became the default answer for anyone chasing sub-second cold starts on Kubernetes or Lambda. That default is now worth questioning. In March 2026, Java 26 shipped JEP 516: Ahead-of-Time Object Caching with Any GC, the latest piece of Project Leyden’s startup work, and the first version of AOT caching that plays nicely with ZGC. Combined with JEP 483, JEP 514, and JEP 515 from JDK 25, Leyden now delivers a meaningful chunk of Native Image’s startup benefit without a native build, without reachability metadata, and without a single line of application code changing.
So the real question isn’t whether Leyden works. It clearly does. The real question is whether Native Image’s build complexity is still worth paying for most workloads, or whether it’s become a tax teams keep paying out of habit.
What Project Leyden Actually Shipped
Project Leyden’s core idea is simple: move work the JVM normally does at startup into an earlier, one-time training run, then replay the results instead of redoing the work every time. That idea has arrived in four incremental JEPs, each solving one specific piece of the problem.
JEP 483: Ahead-of-Time Class Loading and Linking (JDK 24)
This was the foundational piece. A training run records which classes your application loads, parses, and links, then stores them in an already-linked state inside an AOT cache file. On the next startup, the JVM skips re-parsing and re-verifying those classes entirely. According to coverage of the feature, this alone let Spring PetClinic start roughly 41% faster, with around 21,000 classes appearing already linked at boot.
JEP 514: Ahead-of-Time Command-Line Ergonomics (JDK 25)
JEP 483 required two separate commands: one to record a training run, another to assemble the cache. That friction mattered, because most teams will not adopt a feature that needs a bespoke build pipeline. JEP 514 collapsed those two steps into one, so a single flag now handles both training and assembly. This is a small change on paper, but it is the difference between “an interesting JVM flag” and “something a CI pipeline can realistically call.”
JEP 515: Ahead-of-Time Method Profiling (JDK 25)
Class loading is not the only thing that’s slow. Even after a JVM starts, the JIT compiler needs time to observe which methods run hottest before it compiles them to native code, a delay known as warmup. JEP 515 folds method-execution profiles from the training run into the AOT cache, so production startup begins with prior knowledge already in hand instead of learning it from scratch. Reported warmup improvements sit in the 15 to 25 percent range compared to earlier JDKs.
JEP 516: Ahead-of-Time Object Caching with Any GC (JDK 26)
This is the newest piece, and it closes a real gap. Previous AOT caches stored objects in a format tied to a specific garbage collector, which made them incompatible with ZGC, the low-latency collector many cloud-native teams already choose for its sub-millisecond pause times. As the JEP 516 proposal explains, the fix is to store object references as GC-agnostic logical indices rather than raw memory addresses, then stream and remap them into memory at startup. The result: teams running ZGC no longer have to choose between low-latency garbage collection and AOT-accelerated startup. They can finally have both.
What the Numbers Actually Look Like
Marketing language about “faster startup” is easy to write and hard to trust. Actual measurements are more useful, so here is what independent testing has reported across JDK 24 and JDK 25 AOT caches on real Spring Boot and Quarkus applications.
- A Spring Boot 3.3+ project measured startup dropping from 4.9 seconds to 2.4 seconds, roughly a 51% reduction, using nothing more than the AOT cache workflow, as documented in a JDK 25 upgrade retrospective.
- Spring PetClinic itself started roughly 40 to 41% faster on early JEP 483 builds, per InfoQ’s coverage of the feature.
- A Quarkus application called SwiftShip saw median readiness time fall from 1,022.4 ms to 380.3 ms, a 63% improvement, after adopting the JDK 25 AOT cache, according to a detailed Quarkus and Leyden benchmark.
- A separate hands-on test reported one sample Spring Boot application going from 1.1 seconds to 0.27 seconds, roughly a 4x improvement, documented on Piotr’s TechBlog.
None of these numbers come from the same benchmark suite, so treat them as directional rather than precisely comparable. Still, a consistent pattern emerges: AOT caching routinely cuts Java startup time by half or more, using two commands and zero application code changes.
How That Stacks Up Against Native Image
Here is where the provocation gets interesting. GraalVM Native Image remains dramatically faster in absolute terms. One widely cited benchmark table puts plain JVM startup at 2.8 seconds against a native image booting in 65 milliseconds, a gap of more than 40x. But “dramatically faster” and “worth the cost” are two different questions, and Leyden changes the second one more than the first.
Cold-Start Time by Deployment Mode

Native Image still wins the raw speed contest by a wide margin. But look at where the biggest jump happens: going from a cold plain JVM to a Leyden AOT cache already captures most of the practically meaningful improvement, the difference between missing a two-second Kubernetes readiness probe and comfortably clearing it. The remaining gap to Native Image’s 65 milliseconds matters for a narrower set of workloads than it used to.
The Trade-Off Leyden Doesn’t Remove: Memory
This is the part of the story that gets glossed over in most “Leyden killed Native Image” takes, and it shouldn’t be. Startup time is not the only reason teams reach for native compilation. The Quarkus team, who integrated Leyden support directly, were candid about this in their own writeup: “in terms of RSS, Project Leyden does not significantly change the picture, while native executables offer a substantially lower memory footprint.” Reducing memory usage was simply never Leyden’s goal.
Memory Footprint at Startup

This is the crux of the whole debate. If your bottleneck is cold-start latency on a readiness probe or a Lambda invocation, Leyden now gets you most of the way there with a fraction of the effort. If your bottleneck is the per-pod memory bill across a fleet of hundreds of instances, Native Image’s smaller footprint is still doing work that Leyden was never designed to do.
Side by Side
| Factor | Plain JVM | Leyden AOT Cache | GraalVM Native Image |
|---|---|---|---|
| Typical startup improvement | Baseline | 50–75% faster in reported tests | 10–40x faster in reported tests |
| Memory footprint | Baseline | Roughly unchanged (Quarkus’s own finding) | Substantially smaller |
| Code changes required | None | None | Often none for simple apps, but reflection/proxy hints for the rest |
| Build process | Standard build | One extra training run, same JVM build | Separate closed-world compilation, several minutes |
| Dynamic features (reflection, dynamic class loading) | Fully supported | Fully supported | Requires explicit configuration or hints |
| JIT available as a runtime safety net | Yes | Yes | No |
So, Is Native Image Still Worth It?
For a meaningful slice of workloads, the honest answer is: probably not anymore, at least not by default. If a service currently misses its Kubernetes readiness SLA on a plain JVM but would comfortably clear it with a 50 to 75% startup improvement, Leyden gets you there for the cost of one extra command in your CI pipeline. That is a much smaller ask than a closed-world build, reachability metadata, and several extra minutes on every build.
Native Image still earns its complexity in narrower, specific situations. Memory-constrained environments, where every megabyte of RSS across hundreds of pods shows up on a cloud bill, are the clearest case, since Leyden was never designed to touch that number. Extremely short-lived functions, where even 380 milliseconds is too slow, are another. And workloads that genuinely need the smallest possible container image, rather than just a fast one, still favor a native binary over a JVM plus an AOT cache file sitting next to it.
Quick Checklist: Do You Still Need Native Image?
- Measure your actual cold-start requirement first. If a Leyden AOT cache alone clears your readiness SLA, you may not need to go further.
- Check whether memory footprint, not startup time, is your real cost driver. If it is, Native Image still has a clear edge Leyden doesn’t touch.
- Confirm how much your application leans on reflection, dynamic proxies, or runtime class loading, since those add real Native Image configuration work that Leyden doesn’t require at all.
- Weigh build time honestly. A training run adds seconds; a native image build adds minutes, which matters across dozens of CI runs a day.
- Remember Leyden keeps the JIT as a fallback for any behavior that diverges from the training run. Native Image has no such safety net.
What We Have Learned
Project Leyden’s four JEPs, 483, 514, 515, and now 516, have turned Java’s startup story from a genuine weakness into a solvable engineering problem, using nothing more than a training run and a couple of JVM flags. JEP 516 specifically closes the last major gap by making the AOT cache work with ZGC, so low-latency garbage collection and fast startup are no longer mutually exclusive choices.
The reported numbers back this up: reductions of 50 to 75% in real Spring Boot and Quarkus applications, with zero code changes. That is enough to make Native Image the wrong default reflex for a large share of the workloads that currently reach for it out of habit. It is not, however, enough to make Native Image obsolete. Memory footprint, the smallest possible container image, and the most extreme cold-start requirements still belong to native compilation, because Leyden was deliberately built to leave that ground uncontested. The right move going forward isn’t picking a permanent winner; it’s measuring your actual bottleneck before reaching for the heavier tool.

