docs
Spring Boot's Ahead-of-Time (AOT) processing enables compilation to GraalVM native images for faster startup, lower memory footprint, and improved runtime performance.
import org.springframework.boot.SpringApplicationAotProcessor;
import org.springframework.boot.AotInitializerNotFoundException;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;AOT processing analyzes the Spring application at build time to generate:
This eliminates the need for runtime reflection and enables instant startup times (typically <100ms vs 2-3 seconds for JVM).
The main entry point for AOT processing during build time.
package org.springframework.boot;
import org.springframework.context.aot.ContextAotProcessor;
import org.springframework.context.support.GenericApplicationContext;
/**
* Entry point for AOT processing of a SpringApplication.
* For internal use only - typically invoked by build tools (Maven/Gradle plugins).
*
* @since 3.0.0
*/
public class SpringApplicationAotProcessor extends ContextAotProcessor {
/**
* Create a new processor for the specified application and settings.
*
* @param application the application main class
* @param settings the general AOT processor settings (output paths, etc.)
* @param applicationArgs the arguments to provide to the main method
*/
public SpringApplicationAotProcessor(Class<?> application,
Settings settings,
String[] applicationArgs);
/**
* Process the application and generate AOT artifacts.
* Prepares application context, performs AOT processing, and writes output.
*/
public void process() throws Exception;
/**
* Main method for command-line invocation (used by build plugins).
*
* Usage: SpringApplicationAotProcessor <applicationMainClass> <sourceOutput>
* <resourceOutput> <classOutput> <groupId> <artifactId> [originalArgs...]
*/
public static void main(String[] args) throws Exception;
}Exception thrown when the AOT-generated initializer class cannot be found at runtime.
package org.springframework.boot;
/**
* Exception thrown when the AOT initializer couldn't be found.
* This typically occurs when running with spring.aot.enabled=true but the
* AOT processing phase was not executed or failed.
*
* @since 3.2.6
*/
public class AotInitializerNotFoundException extends RuntimeException {
/**
* Create exception with main class and initializer class name.
*
* @param mainClass the application main class
* @param initializerClassName the expected initializer class name
*/
public AotInitializerNotFoundException(Class<?> mainClass, String initializerClassName);
/**
* Get the application main class that failed to find its initializer.
*
* @return the main class
*/
public Class<?> getMainClass();
}<project>
<properties>
<java.version>21</java.version>
<graalvm.version>23.0.1</graalvm.version>
</properties>
<build>
<plugins>
<!-- Spring Boot Maven Plugin with AOT support -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<image>
<builder>paketobuildpacks/builder:tiny</builder>
<env>
<BP_NATIVE_IMAGE>true</BP_NATIVE_IMAGE>
</env>
</image>
</configuration>
<executions>
<execution>
<id>process-aot</id>
<goals>
<goal>process-aot</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- Native Build Tools Plugin -->
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<configuration>
<classesDirectory>${project.build.outputDirectory}</classesDirectory>
<metadataRepository>
<enabled>true</enabled>
</metadataRepository>
</configuration>
<executions>
<execution>
<id>add-reachability-metadata</id>
<goals>
<goal>add-reachability-metadata</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>plugins {
id 'java'
id 'org.springframework.boot' version '4.0.0'
id 'io.spring.dependency-management' version '1.1.0'
id 'org.graalvm.buildtools.native' version '0.10.0'
}
java {
sourceCompatibility = '21'
}
// Native image configuration
graalvmNative {
binaries {
main {
imageName = 'myapp'
mainClass = 'com.example.MyApplication'
buildArgs.add('--verbose')
buildArgs.add('-H:+ReportExceptionStackTraces')
}
}
metadataRepository {
enabled = true
}
}
// Enable AOT processing
tasks.named('processAot') {
args = []
}# Maven: Build native image
./mvnw -Pnative native:compile
# Maven: Build native image using Buildpacks
./mvnw spring-boot:build-image
# Gradle: Build native image
./gradlew nativeCompile
# Gradle: Build native image using Buildpacks
./gradlew bootBuildImage
# Run the native executable
./target/myapp
# Or on Windows
./target/myapp.exeProvide hints to GraalVM about reflection, resources, proxies, and JNI.
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ImportRuntimeHints;
@SpringBootApplication
@ImportRuntimeHints(MyApplication.MyRuntimeHints.class)
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
// Register custom runtime hints
static class MyRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
// Reflection hints for classes loaded dynamically
hints.reflection()
.registerType(MyDynamicallyLoadedClass.class, builder ->
builder.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS,
MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS));
// Resource hints for files loaded at runtime
hints.resources()
.registerPattern("config/*.properties")
.registerPattern("data/*.json");
// Proxy hints for dynamic proxies
hints.proxies()
.registerJdkProxy(MyInterface.class, Serializable.class);
// JNI hints for native method access
hints.jni()
.registerType(MyNativeClass.class, builder ->
builder.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS));
// Serialization hints
hints.serialization()
.registerType(MySerializableClass.class);
}
}
}import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.test.context.aot.DisabledInAotMode;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@ExtendWith(SpringExtension.class)
class MyApplicationTests {
@Test
void contextLoads() {
// Test application starts successfully
}
@Test
@DisabledInAotMode // Skip this test in AOT/native mode
void testWithReflection() {
// Test that uses reflection not supported in native image
}
}Run tests against native image:
# Maven: Test native image
./mvnw -PnativeTest test
# Gradle: Test native image
./gradlew nativeTestProblem: Application fails to start with AotInitializerNotFoundException.
Startup with AOT mode enabled failed: AOT initializer __ApplicationContextInitializer
could not be foundCauses:
Solution:
# Ensure AOT processing runs during build
./mvnw clean package -Pnative
# Check AOT output directory exists
ls -la target/spring-aot/main/sources
# Verify initializer class was generated
find target/spring-aot -name "*__ApplicationContextInitializer.java"
# Review build logs for AOT processing errors
./mvnw clean package -X | grep "process-aot"Problem: ClassNotFoundException or NoSuchMethodException at runtime.
Solution: Add runtime hints for dynamically loaded classes:
@ImportRuntimeHints(MyRuntimeHints.class)
@SpringBootApplication
public class MyApplication {
static class MyRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
// Register all classes from a package
hints.reflection()
.registerType(TypeReference.of("com.example.MyClass"),
MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS,
MemberCategory.INVOKE_PUBLIC_METHODS);
}
}
}Problem: FileNotFoundException for resources loaded at runtime.
Solution: Register resource patterns:
hints.resources()
.registerPattern("application*.properties")
.registerPattern("templates/*.html")
.registerPattern("static/**")
.registerResourceBundle("messages");Problem: Proxy creation fails at runtime.
Solution: Register proxy interfaces:
hints.proxies()
.registerJdkProxy(MyInterface.class)
.registerJdkProxy(FirstInterface.class, SecondInterface.class);proxyTargetClass=false)@Profile("native") for native-specific beansFROM ghcr.io/graalvm/native-image:ol8-java21 AS builder
WORKDIR /build
# Copy project files
COPY pom.xml .
COPY src ./src
# Build native image
RUN ./mvnw package -Pnative -DskipTests
# Runtime stage - minimal image
FROM gcr.io/distroless/base-debian11
COPY --from=builder /build/target/myapp /app/myapp
ENTRYPOINT ["/app/myapp"]| Metric | JVM Mode | Native Mode |
|---|---|---|
| Startup Time | 2-3s | <100ms |
| Memory Usage (RSS) | 200-500MB | 50-100MB |
| First Request Latency | High | Low |
| Steady-State Throughput | High | Comparable |
| Build Time | <1min | 3-5min |
| Image Size | 150-250MB | 80-120MB |
# Enable AOT mode explicitly
export SPRING_AOT_ENABLED=true
# Native image specific configuration
export SPRING_NATIVE_REMOVE_UNUSED_AUTOCONFIG=true
export SPRING_NATIVE_REMOVE_YAML_SUPPORT=false