skip to content

leyden: starting fast

AOT caches today, AOT code next.

chapter
chapter 18.02
reading time
8 min read
preview

Project Leyden makes java start and warm up faster by shifting work in time: things every run used to redo (loading and linking classes, profiling, now compiling) happen once, in a training run, and get saved in an AOT cache that later runs map in.

as of october 2026, the cache holds loaded and linked classes (java 24), method profiles (25) and heap objects for any GC (26). compiled code (JEP 544) is targeted to jdk 28. the long-term idea, condensers, is still a design note.

the picture

you know how a JVM gets faster the longer it runs, and the first minute is the slow part? that's the JVM rediscovering, every single time, what your app needs: which classes, linked how, which methods are hot, and what to compile them into. the AOT and Leyden chapter shows how to use what exists today, with measurements. this one is about where it's headed.

think of a theatre. a touring show that builds its sets from planks every night before the curtain goes up spends most of the evening building. Leyden is a dress rehearsal (the training run) where the crew builds everything once and loads it onto the truck (the cache). every show after that just wheels the sets out. each java release lets the crew pre-build one more kind of thing.

a spectrum, not a switch

java's startup story used to be two extremes: the fully dynamic JVM (slow to start, fast at peak, everything works) and a native image (instant start, a closed world). Leyden's bet is that you shouldn't have to pick an end. you should be able to move along the line, shifting more work earlier and accepting only the constraints you actually need.

← every run does the workdone before the run →
AOT code · 28, targetedtargeted to jdk 28 (march 2027)

moved earliercompilation: C1 and C2 code for hot methods, compiled during training and stored in the cache, so they start out compiled. JEP 544 reports 65 to 80% faster startup on five framework benchmarks, against 50 to 70% for the cache alone.

you give upthe code only loads on the same CPU features and the same GC as the training run; otherwise it's skipped (the classes and profiles still work). and a bigger cache.

click a stop. left is everything at run time, right is everything at build time. each stop says what moved earlier and what it costs. green ships today, amber is targeted to jdk 28 (as of october 2026), the rest are a design and a separate toolchain.

notice what every stop up to AOT code has in common: you give up almost nothing. reflection, dynamic class loading, agents, all of it still works, because the cache is a head start, not a contract. if the cache doesn't fit the run (wrong JDK, different classpath, a class that changed), the JVM just does the work itself. that's the difference from a native image, and it's why Leyden can ship in pieces.

what jdk 28 adds

if JEP 544 lands as targeted, the commands don't change at all. same training run, same flag in production, and the cache now also carries machine code:

terminal
# jdk 28 (JEP 544, targeted). illustrative: the same two commands as on jdk 25
java -XX:AOTCacheOutput=app.aot -jar app.jar     # training run, then the cache is assembled
java -XX:AOTCache=app.aot -jar app.jar           # production: hot methods start out compiled

hover a stage.

what goes into the cache by jdk 28, and where each piece comes from. every piece is optional at run time: whatever doesn't match is skipped and done the normal way.

JEP 544's own measurements (jdk 28 early access, a two-core linux x64 machine, so treat them as the authors' numbers, not yours):

benchmarkAOT cache alonecache + AOT code
startup, five framework benchmarksabout 50 to 70% fasterabout 65 to 80% faster
warmup, javac, first of 20 iterationsabout 30% fasterabout 75% faster, near steady state by the 4th

AOT code doesn't replace the JIT. it's a starting point: if a method later turns out to need different optimizations (the code was compiled for what training saw), it's deoptimized and the JIT takes over as usual. and the code is only used where it fits: the JEP supports aarch64 and x64 at first, needs the same CPU features as training, and needs the same garbage collector, because compiled code has the collector's barriers baked in.

quick check
jdk 28: the cache was trained with the default G1, and production runs with -XX:+UseZGC. what happens to the AOT code?

condensers: the long game

the leyden project's design note "Toward Condensers" (2023) describes where this goes. a condenser is a transformation you run on your application ahead of time. it can shift computation earlier (precompute, preinitialize, compile), and it can do more if you let it constrain the program: promise that no new classes appear at run time, say, and a condenser can specialize much harder. condensers compose, and the output of each is still a normal java application, so you can stop at whatever point on the spectrum you're comfortable with.

the AOT cache is the first practical piece of that idea (its "transformation" is a training run). as of october 2026 there's no condenser JEP and no API, just the design and the premain branch where prototypes happen.

jdk 28

compiled C1 and C2 code for hot methods goes into the cache too. same commands as on 25. aarch64 and x64 only at first, and the code is GC-specific.

leyden's JEPs and plans, plus one startup JEP for ZGC, checked against the JEP pages and the leyden project page (october 2026). click a row.
keylines
  • leyden = shift work from every run to a training run, and save it in an AOT cache.
  • shipped: loaded + linked classes (24), one-step training and profiles (25), objects for any GC (26).
  • targeted for jdk 28: compiled code in the cache (JEP 544). same commands, needs the same CPU and GC.
  • every piece is a head start, not a contract: if it doesn't match, the JVM does the work itself.
  • the endgame is condensers: a spectrum of opt-in constraints. as of october 2026, still a design note.