AOT Class Loading and Caching in Java
Every morning a school canteen cook could chop the same onions, measure the same rice and grind the same masala before cooking anything. A smart cook does that preparation once, keeps it ready in the fridge, and starts cooking the moment the first student arrives. A Java program does a lot of the same preparation every time it starts: it finds classes, reads them, checks them and links them together. Ahead-of-time (AOT) class loading and caching lets the JVM do that preparation once and reuse it on every later start. In this lesson, you will learn what an AOT cache is, how to create and use one, and when it helps.
What Is an AOT Cache?
An AOT cache in Java is a file that is used to store classes that are already loaded and linked, recorded during a training run of your application. On later runs, the JVM maps this file into memory, so those classes are ready to use immediately instead of being found, parsed, verified and linked again.
It arrived in two steps:
| Release | What was added |
|---|---|
| Java 24 | AOT class loading and linking, with a two-step workflow (record, then create) |
| Java 25 | A one-step workflow (-XX:AOTCacheOutput) and method profiles stored in the cache, so the JIT compiler warms up sooner |
It builds on an older feature called Class Data Sharing (CDS), which only stored parsed classes. The AOT cache goes further: classes arrive already linked, and some objects and profiling data come with them.
Why Startup Time Matters
| Situation | Why a faster start helps |
|---|---|
| Command-line tools | Users wait for every run |
| Serverless functions | You often pay for every millisecond, and cold starts are common |
| Containers that scale up | New copies must serve traffic quickly |
| Tests and build tools | They start the JVM many times a day |
A long-running server that starts once a month gains little.
Creating and Using a Cache
The starting point is an ordinary JAR. Here it is app.jar, a small fee report that groups fees by student with streams.
Step 1: the training run. Run the application once with -XX:AOTCacheOutput. It runs normally, and when it ends the JVM writes the cache.
java -XX:AOTCacheOutput=app.aot -jar app.jarAdvitha 16,000
Navika 30,500
Srihitha 14,000
Reading AOTConfiguration app.aot.config and writing AOTCache app.aot
AOTCache creation is complete: app.aot 11567104 bytes
Removed temporary AOT configuration file app.aot.configStep 2: every later run. Point the JVM at the cache with -XX:AOTCache.
java -XX:AOTCache=app.aot -jar app.jarAdvitha 16,000
Navika 30,500
Srihitha 14,000Explanation:
- The training run printed the normal report, then recorded every class the program loaded and wrote them, already linked, into
app.aot(about 11 MB here, because it includes the JDK classes the program used). - The production run prints exactly the same output. The cache changes how fast the program starts, never what it does.
- On the machine used for this lesson, the median start-to-finish time over 15 runs dropped from about 30 ms to about 22 ms. Small programs gain a little; large applications with thousands of classes (web frameworks, for example) often start 40 percent or more faster.
The Two-Step Workflow
Java 24 needed two commands, and they still work in Java 25. They are useful when the training run happens on one machine and the cache is assembled later, or in a separate build stage.
java -XX:AOTMode=record -XX:AOTConfiguration=app.aotconf -jar app.jar
java -XX:AOTMode=create -XX:AOTConfiguration=app.aotconf -XX:AOTCache=app.aot -jar app.jar...
AOTConfiguration recorded: app.aotconf
Reading AOTConfiguration app.aotconf and writing AOTCache app.aot
AOTCache creation is complete: app.aot 11583488 bytesThe first command runs the application and records what it loads into app.aotconf. The second doesn't run the application; it only builds the cache from that recording.
Options
| Option | Description |
|---|---|
-XX:AOTCacheOutput=<file> | It is used to run a training run and write the cache in one step (Java 25). |
-XX:AOTCache=<file> | It is used to start the application with an existing cache. |
-XX:AOTMode=record | It is used to record a training run into a configuration file (Java 24 workflow). |
-XX:AOTMode=create | It is used to build the cache from a configuration file. |
-XX:AOTMode=on | It makes the JVM stop with an error if the cache can't be used, instead of quietly running without it. |
-Xlog:aot | It prints what the JVM does with the cache, useful to check that it was used. |
The Cache Must Match
The cache describes one exact setup. When you start the JVM with a cache, these must be the same as in the training run:
- the same JDK build (same version and vendor),
- the same class path (same JAR names, in the same order), and
- the same operating system and CPU type.
If anything differs, the JVM prints an error such as shared class paths mismatch and then runs normally without the cache. Your program still works; it just doesn't start faster.
In Practice
- Train with realistic work. The cache only holds classes that the training run actually loaded. A training run that exits immediately captures little. Run a typical request or a test suite.
- Build the cache in your pipeline. In a container build, run the training step in the same image you deploy, then copy the cache next to the JAR.
- Rebuild on every change. A new JAR or a JDK update means a new cache.
- No code changes. Unlike tools that compile Java to a native program, the AOT cache needs nothing from your source code and keeps every Java feature (reflection, dynamic class loading) working.
Common Mistakes
Changing the JAR name or path after training. -cp other.jar when the cache was trained with app.jar is a mismatch, even if the contents are identical.
Training with a different JDK. A cache made on JDK 25.0.1 isn't used by JDK 25.0.2.
Expecting a faster program. The cache speeds up startup and warm-up. Code that runs for hours runs at the same speed.
Committing the cache file to Git. It is a large build output, specific to one JDK and platform. Generate it in the build instead.
Practice
- Easy: Package any small program from an earlier lesson as a JAR. Create a cache with
-XX:AOTCacheOutput, then run it with-XX:AOTCacheand-Xlog:aotto see the cache being opened. - Harder: Start the same program with
-XX:AOTCache=app.aot -XX:AOTMode=onbut a renamed JAR. Read the error, then explain why the default mode (withouton) still runs the program.
Key Takeaways
Was this tutorial helpful?