JEP 514: Ahead-of-Time Command-Line Ergonomics Explained

  • Last Updated: January 20, 2026
  • By: javahandson
  • Series
img

JEP 514: Ahead-of-Time Command-Line Ergonomics Explained

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.

1. JIT vs AOT

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:

  • Classes must be discovered and loaded
  • Bytecode must be verified and linked
  • Frequently used methods must be compiled at runtime

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

2. What is an AOT cache, and why does it improve startup?

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.

2.1. What does an AOT cache contain?

An AOT cache typically contains:

  • Information about which classes are actually loaded
  • Class linking and resolution metadata
  • Precomputed relationships between classes and modules
  • Other JVM startup data discovered during the training run

This allows the JVM to avoid repeating the same discovery and setup work on every startup.

2.2. How does an AOT cache help during JVM startup?

Without an AOT cache, the JVM starts with no prior knowledge:

  • It scans the classpath
  • Loads and verifies required classes
  • Links classes and prepares runtime structures

With an AOT cache:

  • The JVM already knows which classes it needs
  • Much of the linking and setup work is reused
  • Startup becomes faster and more predictable

The JVM effectively starts with a prepared startup plan instead of starting from scratch.

2.3. Why does this matter in modern Java deployments?

AOT caches are especially valuable in environments where startup time directly affects performance, scalability, or cost.

Microservices

  • Services start and restart frequently
  • Faster startup improves auto-scaling and resilience
  • Reduced cold-start impact during traffic spikes

Containers

  • Containers are short-lived by design
  • Quicker startup means faster readiness and health checks
  • Better fit for Kubernetes and cloud-native platforms

Serverless and short-lived processes

  • Applications may run for only seconds or minutes
  • Cold-start time can dominate total execution time
  • Faster startup leads to better responsiveness and lower costs

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.

3. Life before JEP 514: The two-step AOT workflow

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:

  • The application executes normally
  • The JVM records startup-related behavior
  • The output is an AOT configuration file (app.aotconf)

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:

  • The AOT cache file (app.aot) is created
  • The configuration file is no longer needed
  • The cache can now be used in production runs

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.

3.1. Why was this workflow painful?

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.

4. JEP 514 in action: One-step AOT Cache creation

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.

4.1. How the one-step workflow works

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.

4.2. Creating an AOT cache with a single command

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:

  • Runs the application for training
  • Creates the AOT cache (app.aot)
  • Automatically cleans up intermediate artifacts

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.

5. Running with an AOT Cache (Production usage)

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.

5.1. What changes when an AOT cache is used?

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.

5.2. What does not change?

Using an AOT cache does not change:

  • Application logic
  • Runtime semantics
  • JIT compilation behavior after startup

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.

5.3. Backward compatibility and safety

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:

  • Existing applications remain fully compatible
  • Using an AOT cache is opt-in
  • Removing the option simply restores the original startup behavior

JEP 514 improves how AOT caches are created, but production usage remains stable, predictable, and backward compatible.

6. Advanced control with JDK_AOT_VM_OPTIONS

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.

6.1. What problem does JDK_AOT_VM_OPTIONS solve?

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.

6.2. How it keeps the one-step workflow flexible

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:

  • Training reflects the intended runtime environment
  • Cache creation can use its own JVM configuration
  • Developers do not need to manually split the workflow

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.

7. When we should still use the Two-Step Workflow

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.

7.1. Training and cache creation on different machines

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:

  • Training to accurately reflect real-world usage
  • Cache creation to benefit from additional resources

The JEP notes that this separation may become increasingly important as future AOT optimizations introduced by Project Leyden grow more complex.

7.2. Memory requirements of the one-step workflow

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.

7.3. Resource-constrained environments

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:

  • Run the training and cache creation steps separately
  • Adjust JVM options independently
  • Reduce peak resource usage

This provides more control over how resources are consumed during AOT cache creation.

7.4. Preparing for future Leyden optimizations

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.

8. Design rationale and future direction

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.

8.1. Why AOTMode=record+create was not chosen

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:

  • Using an existing AOT cache (-XX:AOTCache)
  • Creating a new AOT cache (-XX:AOTCacheOutput)

This clarity ensures that command-line options remain understandable and unambiguous as new AOT features are introduced.

8.2. Avoiding ambiguity in future AOT workflows

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.

8.3. Preparing Java for Project Leyden

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.

8.4. A small change with strategic impact

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.

9. Conclusion

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.

Leave a Comment