JEP 514: Ahead-of-Time Command-Line Ergonomics Explained
-
Last Updated: January 20, 2026
-
By: javahandson
-
Series
Learn Java in a easy way
JEP 514 introduces Ahead-of-Time command-line ergonomics in Java, making AOT cache creation easier through a single command while preserving existing workflows, improving startup performance, and paving the way for future Project Leyden optimizations.
In a traditional Java application, the JVM relies heavily on Just-In-Time (JIT) compilation. With JIT, Java bytecode is compiled into native machine code while the application is running. This allows the JVM to apply smart optimizations based on how the code is actually used, but it also means extra work during startup.
Because of this, application startup is slower:
This overhead is acceptable for long-running applications, but it becomes a problem for microservices, containers, and short-lived Java processes.
Ahead-of-Time (AOT) takes a different approach. Instead of waiting until runtime, the JVM performs part of this work in advance and stores the results in an AOT cache. When the application starts, the JVM can reuse this cached information, avoiding much of the early startup cost.
In simple terms:
JIT → optimize while the app is running
AOT → prepare important work before the app starts
An Ahead-of-Time (AOT) cache is a file created by the JVM using information collected during a dedicated training run of an application. During this training run, the JVM observes how the application starts and records startup-related details. This recorded data is then used to generate an AOT cache, which can be reused in later production runs.
The key idea is simple: do some of the startup work in advance and reuse it later.
An AOT cache typically contains:
This allows the JVM to avoid repeating the same discovery and setup work on every startup.
Without an AOT cache, the JVM starts with no prior knowledge:
With an AOT cache:
The JVM effectively starts with a prepared startup plan instead of starting from scratch.
AOT caches are especially valuable in environments where startup time directly affects performance, scalability, or cost.
Microservices
Containers
Serverless and short-lived processes
AOT caches were introduced in earlier Java releases, but using them required manual, multi-step commands. JEP 514 does not change what an AOT cache is — it makes creating one significantly easier.
Before JEP 514, creating an AOT cache in Java required a manual two-step process. Developers had to explicitly run the JVM twice, each time in a different AOT mode, to first record application behavior and then create the cache.
This workflow was introduced earlier and worked correctly, but it was cumbersome for common use cases.
Step 1: Training Run (AOTMode=record)
In the first step, the application is started in record mode. In this mode, the JVM observes the application during startup and execution and records relevant information into an AOT configuration file.
$ java -XX:AOTMode=record \
-XX:AOTConfiguration=app.aotconf \
-cp app.jar com.javahandson.App ...
During this run:
This configuration file is an intermediate artifact and is not used at runtime.
Step 2: Cache Creation (AOTMode=create)
In the second step, the JVM is launched again, this time in create mode. It reads the previously recorded AOT configuration and produces the actual AOT cache.
Example:
$ java -XX:AOTMode=create \
-XX:AOTConfiguration=app.aotconf \
-XX:AOTCache=app.aot
After this step:
Step 3: Running the Application with the AOT Cache
Once the cache is created, the application is started by simply referencing it:
$ java -XX:AOTCache=app.aot \
-cp app.jar com.javahandson.App ...
This run benefits from faster startup because the required classes and metadata are available from the cache.
Although the two-step AOT workflow worked correctly, it was inconvenient for everyday use. Creating a single AOT cache required two separate JVM executions—one for the training run and another for cache creation. This added extra steps even for the most common scenarios, making the process feel heavy and error-prone.
In addition, the workflow produced a temporary AOT configuration file that developers had to manage explicitly. This file was only an intermediate artifact, yet it had to be named, passed between commands, and eventually deleted manually. As a result, developers had to carefully coordinate the entire flow: first running the application in record mode, then running it again in create mode, and finally cleaning up intermediate files.
Because of this manual orchestration, using AOT caches was less approachable, especially in automated environments such as CI pipelines or in tools that manage their own training workloads. The complexity discouraged adoption, even though the underlying AOT technology itself was sound.
JEP 514 introduces a new JVM command-line option, -XX:AOTCacheOutputwhich simplifies the creation of an AOT cache for common use cases. With this option, developers no longer need to manually orchestrate multiple JVM invocations to perform training and cache creation.
When -XX:AOTCacheOutput If used by itself, without explicitly specifying other AOT-related options, the Java launcher automatically performs the necessary steps to generate the cache.
Behind the scenes, the launcher effectively splits the invocation into two internal sub-invocations. The first sub-invocation performs a training run, equivalent to running the JVM in AOTMode=record. The second sub-invocation then creates the AOT cache, equivalent to AOTMode=create. This behavior is explicitly described in the JEP and does not introduce any new AOT mechanism.
As part of this process, the JVM creates a temporary AOT configuration file to pass information between the two phases. This configuration file is automatically managed by the JVM and is deleted once the cache creation step completes, removing the need for manual cleanup.
The earlier two-step workflow can now be replaced with a single command:
$ java -XX:AOTCacheOutput=app.aot \
-cp app.jar com.javahandson.App ...
This single command:
The resulting cache is identical to one created using the manual two-step process and can be used in production in the same way as before.
4.3. No loss of power, just better ergonomics
JEP 514 does not remove or restrict existing AOT workflows. Developers can still explicitly use AOTMode=record and AOTMode=create when needed. The goal of this JEP is purely to improve command-line ergonomics, making AOT cache creation easier and more accessible while preserving the full expressiveness of existing AOT options.
Once an AOT cache has been created, running the application in production is no different from before JEP 514. The JVM uses the cache when it is explicitly provided, but the way the application is launched remains familiar and unchanged.
To run an application using an AOT cache, you simply specify the cache file using the –XX:AOTCache option:
$ java -XX:AOTCache=app.aot \
-cp app.jar com.javahandson.App ...
This command tells the JVM to load startup-related information from the specified AOT cache instead of discovering everything from scratch.
When an AOT cache is present, the JVM can skip or shorten several early startup steps. Required classes and their linking information are already available, allowing the JVM to initialize the application more quickly. As a result, startup time is reduced and becomes more predictable.
Importantly, this improvement is limited to startup behavior only. Once the application is running, execution proceeds exactly as it would without an AOT cache.
Using an AOT cache does not change:
The application behaves the same way after it has started. The JVM continues to perform runtime optimizations as usual, and there are no changes to how code executes or how results are produced.
JEP 514 does not alter how AOT caches are consumed at runtime. The -XX:AOTCache option works the same way as in earlier releases, and applications that do not use an AOT cache continue to behave exactly as before.
This means:
JEP 514 improves how AOT caches are created, but production usage remains stable, predictable, and backward compatible.
While the one-step AOT workflow introduced by JEP 514 works well for common scenarios, there are cases where the training run and the AOT cache creation step require different JVM options. For example, cache creation may benefit from different memory settings or JVM flags than those used during training.
Before JEP 514, this often forced developers to fall back to the manual two-step workflow, even when a single command would otherwise be sufficient.
JEP 514 introduces a new environment variable, JDK_AOT_VM_OPTIONS, to address this limitation. This variable allows developers to specify JVM options that apply only to the sub-invocation that performs AOT cache creation, without affecting the training run.
This makes it possible to continue using the one-step workflow even when the two phases require different JVM settings.
When -XX:AOTCacheOutput is used, the Java launcher internally runs two sub-invocations. Any options specified through JDK_AOT_VM_OPTIONS are applied only to the cache creation phase. The training run continues to use the JVM options provided on the command line.
This separation ensures that:
The syntax of JDK_AOT_VM_OPTIONS is the same as that of JAVA_TOOL_OPTIONS. It is set as an environment variable before running the Java command.
Example:
export JDK_AOT_VM_OPTIONS="-Xmx4g"
java -XX:AOTCacheOutput=app.aot \
-cp app.jar com.javahandson.App ...
In this example, the additional heap setting is applied only during AOT cache creation, while the training run uses the JVM options specified directly on the command line.
JAVA_TOOL_OPTIONS applies JVM options to every JVM invocation, including both the training run and the cache creation step. In contrast, JDK_AOT_VM_OPTIONS is more specific and affects only the cache creation sub-invocation when using the one-step AOT workflow.
This targeted control is what allows JEP 514 to simplify AOT usage without reducing flexibility.
Although JEP 514 makes it much easier to create an AOT cache using a single command, there are still situations where the manual two-step workflow is more appropriate. The JEP explicitly calls out several such cases, especially in cloud and resource-sensitive environments.
In some deployments, it can be useful to separate the training run from cache creation. For example, you may want to run the training phase on a small instance that closely matches the production environment, while performing the cache creation on a larger instance with more CPU cores and memory.
This allows:
The JEP notes that this separation may become increasingly important as future AOT optimizations introduced by Project Leyden grow more complex.
When using the one-step workflow, -XX:AOTCacheOutput the JVM internally runs two sub-invocations. Each sub-invocation uses its own Java heap, and both heaps are allocated with the same size specified on the command line.
As a result, the total memory required is effectively double the specified heap size. For example, if the command includes -Xms4g -Xmx4gThe environment must have approximately 8 GB of memory available for the workflow to complete successfully.
This behavior is explicitly mentioned in the JEP and is an important consideration for memory-limited systems.
In environments with limited memory or CPU resources, the one-step workflow may fail due to these increased requirements. In such cases, using the two-step workflow allows developers to:
This provides more control over how resources are consumed during AOT cache creation.
The JEP also highlights that future AOT optimizations under Project Leyden may require more time or resources to create an AOT cache. For example, an optimization might take minutes to complete on a small instance but only seconds on a larger one.
In these scenarios, the two-step workflow provides flexibility by allowing cache creation to be performed in a more suitable environment, without affecting how or where the training run is performed.
In summary, JEP 514 simplifies the common case, but it does not replace the two-step workflow. Both approaches remain valid, and developers can choose the one that best fits their deployment and resource constraints.
While designing JEP 514, one obvious alternative was to introduce a combined AOT mode such as AOTMode=record+create. At first glance, this might seem like a simpler solution, as it would combine the training run and cache creation into a single explicit mode.
However, the JEP explains that this approach was intentionally rejected.
Today, a combined mode could work, but it would create problems as AOT evolves. In the future, training runs may be extended to read an existing AOT cache. If that happens, an option like -XX:AOTCache=myapp.aot would become ambiguous—should the JVM read from the cache, write to it, or both?
To avoid this ambiguity, the JEP favors a clearer separation between:
-XX:AOTCache)-XX:AOTCacheOutput)This clarity ensures that command-line options remain understandable and unambiguous as new AOT features are introduced.
By introducing -XX:AOTCacheOutput As a distinct option, JEP 514 preserves a clean and extensible command-line design. Each option has a single, well-defined purpose, which reduces confusion for both users and tooling.
This decision allows future AOT workflows to expand without breaking existing scripts or introducing unclear behavior.
JEP 514 is closely tied to Project Leyden, which aims to improve Java startup time, warm-up behavior, and footprint using ahead-of-time techniques. As Leyden introduces more advanced AOT optimizations, creating AOT caches may become more resource-intensive or time-consuming.
By simplifying how developers access AOT features today, JEP 514 lowers the barrier to adoption and prepares the ecosystem for deeper AOT usage in future Java releases.
JEP 514 does not introduce new optimizations or change how the JVM executes code. Instead, it focuses on command-line ergonomics—making existing capabilities easier to use in everyday workflows. This makes it a small change from an implementation perspective, but a strategic one for long-term adoption.
By reducing friction and preserving flexibility, JEP 514 helps ensure that future AOT improvements can be adopted smoothly across a wide range of Java applications.
JEP 514 does not introduce new Ahead-of-Time optimizations or change how AOT works inside the JVM. Instead, it focuses on something equally important: making AOT easier to use in practice. Simplifying AOT cache creation into a single command removes much of the manual effort that previously discouraged adoption.
At the same time, JEP 514 preserves flexibility. Developers can still use the explicit two-step workflow when needed, apply different JVM options to training and cache creation, and choose the approach that best fits their environment. Nothing is taken away—only friction is reduced.
As Java moves forward with Project Leyden, AOT will play a bigger role in improving startup time and efficiency. JEP 514 prepares the ecosystem for that future by lowering the barrier to entry today. It is a small change on the surface, but a strategic one that makes Java’s AOT capabilities more approachable, practical, and ready for what comes next.