Edit this Page

Ahead-of-Time (AOT) Caching

This guide explains how to leverage OpenJDK’s Ahead-of-Time (AOT) caching capabilities in Quarkus to achieve significantly faster application startup times.

Overview

Starting with JDK 24, Project Leyden introduces powerful AOT caching capabilities (JEP 483) that can significantly reduce Java application startup time. Quarkus provides first-class support for these features, making it easy to build and deploy AOT-optimized applications.

Leyden AOT requires JDK 24 or newer. For older JDK versions, Quarkus can fall back to AppCDS (see AppCDS (Legacy)).

Quick start

To build a Quarkus application with AOT caching enabled:

Maven
./mvnw verify -Dquarkus.package.jar.aot.enabled=true -DskipITs=false (1)
1 If you are using a project generated by our tooling, adding -DskipITs=false makes sure the integration tests will be run.
Gradle
./gradlew build quarkusIntTest buildAotEnhancedImage -Dquarkus.package.jar.aot.enabled=true
When enabling AOT via quarkus.package.jar.aot.enabled=true, Quarkus will automatically switch to the aot-jar JAR packaging if none is explicitly configured via quarkus.package.jar.type.

Then run the application with:

Maven
cd target/quarkus-app
java -XX:AOTCache=app.aot -jar quarkus-run.jar
Gradle
cd build/quarkus-app
java -XX:AOTCache=app.aot -jar quarkus-run.jar

AOT-optimized JAR packaging

Quarkus provides a specialized aot-jar packaging type designed to work optimally with OpenJDK’s AOT features. This packaging type:

  • Structures the application for optimal AOT class loading

  • Delegates all class loading to the JDK’s class loaders (required for AOT to work effectively as Leyden currently does not support custom class loaders)

  • Caches frequently-accessed resources (like service loader files) to avoid classpath scanning at runtime

It is not recommended to use the aot-jar packaging outside of the AOT use case, as the fast-jar packaging offers more optimizations that result in better performance compared to the aot-jar when Leyden’s AOT cache is not used.

Generating the AOT cache

Enable AOT cache generation with:

Maven
./mvnw package -Dquarkus.package.jar.aot.enabled=true
Gradle
./gradle build -Dquarkus.package.jar.aot.enabled=true

On JDK 24+, this generates an app.aot file in target/quarkus-app/ for Maven and build/quarkus-app/ for Gradle.

Training with integration tests

For optimal results, the AOT cache should be "trained" by running actual application workloads. Quarkus can use your @QuarkusIntegrationTest test suite for this training:

Maven
./mvnw verify -Dquarkus.package.jar.aot.enabled=true -DskipITs=false (1)
1 If you are using a project generated by our tooling, adding -DskipITs=false makes sure the integration tests will be run.
Gradle
./gradlew build quarkusIntTest buildAotEnhancedImage -Dquarkus.package.jar.aot.enabled=true

With this approach:

  1. Quarkus builds the application

  2. Integration tests (i.e. tests that are annotated with @QuarkusIntegrationTest) run against the application with AOT training enabled

  3. The JVM captures profiling data during the test run

  4. An optimized app.aot cache is generated

The more representative your integration tests are of production traffic, the more effective the AOT cache will be.

For this approach to work, the integration tests must not be skipped.

With a Maven project generated by our tooling, integration tests are disabled by default and only run for native builds. Enable them with -DskipITs=false.

Controlling the training phase

The quarkus.package.jar.aot.phase configuration controls when training occurs:

  • auto (default): Uses integration-tests for Leyden AOT

  • build: Generates the cache during build (very limited training, faster builds)

  • integration-tests: Generates during integration tests execution (better optimization). For Leyden AOT, this has the same effect as auto

Maven
# Explicit integration test training
./mvnw verify -Dquarkus.package.jar.aot.enabled=true -Dquarkus.package.jar.aot.phase=integration-tests

# Build-time only (builds faster, less optimal)
./mvnw package -Dquarkus.package.jar.aot.enabled=true -Dquarkus.package.jar.aot.phase=build
Gradle
# Explicit integration test training
./mvnw build quarkusIntTest buildAotEnhancedImage -Dquarkus.package.jar.aot.enabled=true -Dquarkus.package.jar.aot.phase=integration-tests

# Build-time only (builds faster, less optimal)
./mvnw build -Dquarkus.package.jar.aot.enabled=true -Dquarkus.package.jar.aot.phase=build

Running with AOT cache

Launch your application with the -XX:AOTCache JVM flag:

Maven
cd target/quarkus-app
java -XX:AOTCache=app.aot -jar quarkus-run.jar
Gradle
cd build/quarkus-app
java -XX:AOTCache=app.aot -jar quarkus-run.jar

You can obtain more information about AOT cache usage with the -Xlog:aot option.

You can collect even more information in an aot.log file with -Xlog:class+load=info,aot+codecache=debug:file=aot.log:level,tags.

Container images

Quarkus can automatically build container images that include AOT caches, giving you fast startup times out of the box.

Building AOT-enhanced container images

When using quarkus-container-image-jib, quarkus-container-image-docker, or quarkus-container-image-podman:

Maven
./mvnw verify -Dquarkus.package.jar.aot.enabled=true -Dquarkus.container-image.build=true -DskipITs=false (1)
1 If you are using a project generated by our tooling, adding -DskipITs=false makes sure the integration tests will be run.
Gradle
./gradlew build quarkusIntTest buildAotEnhancedImage -Dquarkus.package.jar.aot.enabled=true -Dquarkus.container-image.build=true

This will:

  1. Build your application with aot-jar packaging

  2. Create a base container image

  3. Run integration tests to train the AOT cache

  4. Create a new container image with -aot suffix in the version of the image that includes the AOT cache, pre-configured to use it

Adding the container image extension

Add one of the following dependencies to your project:

pom.xml
<!-- Jib (recommended for most use cases) -->
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-container-image-jib</artifactId>
</dependency>

<!-- Or Docker -->
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-container-image-docker</artifactId>
</dependency>

<!-- Or Podman -->
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-container-image-podman</artifactId>
</dependency>
build.gradle
// Jib (recommended for most use cases)
implementation("io.quarkus:quarkus-container-image-jib")

// Or Docker
implementation("io.quarkus:quarkus-container-image-docker")

// Or Podman
implementation("io.quarkus:quarkus-container-image-podman")

Requirements and limitations

  • JDK 24 or newer is required for Leyden AOT features

  • The AOT cache is JDK version specific - you must use the exact same JDK version at runtime as was used to generate the cache

  • Generating the AOT file from the integration tests is currently only supported with the Maven extension. Support will be added for Gradle in the future

  • The feature is untested on Windows environments

Configuration reference

Configuration property fixed at build time - All other configuration properties are overridable at runtime

Configuration property

Type

Default

Whether to automate the creation of an AOT file.

Environment variable: QUARKUS_PACKAGE_JAR_AOT_ENABLED

Show more

boolean

false

The type of AOT file to generate

If auto is used, Quarkus will generate an AOT file for JDK 25+, for older JDKs it will generate an AppCDS file.

Environment variable: QUARKUS_PACKAGE_JAR_AOT_TYPE

Show more

auto, aot, app-cds

The phase in which the AOT file should be generated.

For Leyden AOT, auto means integration-tests.

For AppCDS, auto means build (and an error will be thrown if set to integration-tests).

Environment variable: QUARKUS_PACKAGE_JAR_AOT_PHASE

Show more

auto, build, integration-tests

AppCDS (Legacy)

For JDK versions prior to 24, Quarkus supports Application Class Data Sharing (AppCDS) as a fallback. AppCDS provides more modest startup improvements compared to Leyden AOT, but works on older JDKs.

Generating AppCDS archive

Maven
./mvnw package -Dquarkus.package.jar.aot.enabled=true -Dquarkus.package.jar.aot.type=app-cds
Gradle
./gradlew build -Dquarkus.package.jar.aot.enabled=true -Dquarkus.package.jar.aot.type=app-cds

This generates an app-cds.jsa file.

Running with AppCDS

Maven
cd target/quarkus-app
java -XX:SharedArchiveFile=app-cds.jsa -jar quarkus-run.jar
Gradle
cd build/quarkus-app
java -XX:SharedArchiveFile=app-cds.jsa -jar quarkus-run.jar

AppCDS limitations

  • The paths to the JAR and archive must match exactly between build and runtime

  • The JDK version must be exactly the same

  • AppCDS only caches class metadata, not method profiles or verification results

  • Training during integration tests is not supported for AppCDS (only build-time generation)

The legacy quarkus.package.jar.appcds.enabled configuration is still supported but deprecated. Use quarkus.package.jar.aot.enabled=true with quarkus.package.jar.aot.type=app-cds instead.

Related content

On the same topics