AOT Class Loading and Caching in Java

Naveen AnamaneniUpdated · 7 min read

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:

ReleaseWhat was added
Java 24AOT class loading and linking, with a two-step workflow (record, then create)
Java 25A 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

SituationWhy a faster start helps
Command-line toolsUsers wait for every run
Serverless functionsYou often pay for every millisecond, and cold starts are common
Containers that scale upNew copies must serve traffic quickly
Tests and build toolsThey 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.

Train once, start faster every time

Step 1: the training run. Run the application once with -XX:AOTCacheOutput. It runs normally, and when it ends the JVM writes the cache.

Terminal
java -XX:AOTCacheOutput=app.aot -jar app.jar
Terminal output
Advitha   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.config

Step 2: every later run. Point the JVM at the cache with -XX:AOTCache.

Terminal
java -XX:AOTCache=app.aot -jar app.jar
Terminal output
Advitha   16,000
Navika    30,500
Srihitha  14,000

Explanation:

  1. 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).
  2. The production run prints exactly the same output. The cache changes how fast the program starts, never what it does.
  3. 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.

Terminal
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
Terminal output
...
AOTConfiguration recorded: app.aotconf
Reading AOTConfiguration app.aotconf and writing AOTCache app.aot
AOTCache creation is complete: app.aot 11583488 bytes

The 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

OptionDescription
-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=recordIt is used to record a training run into a configuration file (Java 24 workflow).
-XX:AOTMode=createIt is used to build the cache from a configuration file.
-XX:AOTMode=onIt makes the JVM stop with an error if the cache can't be used, instead of quietly running without it.
-Xlog:aotIt 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

  1. Easy: Package any small program from an earlier lesson as a JAR. Create a cache with -XX:AOTCacheOutput, then run it with -XX:AOTCache and -Xlog:aot to see the cache being opened.
  2. Harder: Start the same program with -XX:AOTCache=app.aot -XX:AOTMode=on but a renamed JAR. Read the error, then explain why the default mode (without on) still runs the program.

Quick check: AOT cache

4 questions
1What does an AOT cache store?
2Which option creates a cache in one step in Java 25?
3What happens by default if the class path doesn't match the cache?
4Does using a cache change what the program prints?

Key Takeaways

Was this tutorial helpful?

Lesson 524 of 578