Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/spring-projects/spring-boot/llms.txt

Use this file to discover all available pages before exploring further.

Ahead-of-time (AOT) processing lets Spring Boot analyze and optimize your application at build time rather than at startup. This is a prerequisite for compiling to a GraalVM native image, and it also makes AOT-generated code available on the JVM for faster startup validation. This guide covers the most common tasks you will encounter when adopting AOT in a Spring Boot project.

How AOT processing works

During the build, the Spring AOT engine processes the application context, evaluates @Conditional annotations against the build-time environment, and generates source code and configuration metadata. That generated code is included in the final artifact alongside the rest of your application.
Conditions evaluated at build time are fixed for the resulting artifact. Beans created under a condition that was true at AOT processing time are always created when the application runs, regardless of the runtime environment. Profile-based configuration that only changes property values (not bean creation) still works at runtime.

Enable AOT processing in your build

Use spring-boot-starter-parent and add the native-maven-plugin. The parent POM defines a native profile that wires the process-aot goal automatically:
pom.xml
<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>3.5.0</version>
</parent>

<build>
  <plugins>
    <plugin>
      <groupId>org.graalvm.buildtools</groupId>
      <artifactId>native-maven-plugin</artifactId>
    </plugin>
  </plugins>
</build>
Run AOT processing explicitly with the native profile active:
./mvnw -Pnative spring-boot:process-aot

Set active profiles during AOT processing

Because @Profile-based conditions are evaluated at build time, you must tell the AOT engine which profiles should be active. Beans that depend on a profile must be created during processing for them to be available at runtime.
Configure the process-aot execution inside the native profile:
pom.xml
<profile>
  <id>native</id>
  <build>
    <pluginManagement>
      <plugins>
        <plugin>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-maven-plugin</artifactId>
          <executions>
            <execution>
              <id>process-aot</id>
              <configuration>
                <profiles>profile-a,profile-b</profiles>
              </configuration>
            </execution>
          </executions>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
</profile>

Provide reflection hints with RuntimeHintsRegistrar

GraalVM native images do not support Java reflection unless explicit hints tell the compiler which classes, methods, and fields need to be accessible. Spring Boot auto-generates most hints, but you may need to add your own for third-party libraries or dynamic code patterns.
1

Implement RuntimeHintsRegistrar

Create a class that implements RuntimeHintsRegistrar and registers the hints you need:
MyRuntimeHints.java
import java.lang.reflect.Method;

import org.springframework.aot.hint.ExecutableMode;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.util.ReflectionUtils;

public class MyRuntimeHints implements RuntimeHintsRegistrar {

    @Override
    public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
        // Register method for reflection
        Method method = ReflectionUtils.findMethod(MyClass.class, "sayHello", String.class);
        hints.reflection().registerMethod(method, ExecutableMode.INVOKE);

        // Register type for java serialization
        hints.reflection().registerJavaSerialization(MySerializableClass.class);

        // Register resources
        hints.resources().registerPattern("my-resource.txt");

        // Register proxy
        hints.proxies().registerJdkProxy(MyInterface.class);
    }

}
2

Activate the registrar with @ImportRuntimeHints

Annotate your @SpringBootApplication class (or any @Configuration class) to activate the registrar:
MyApplication.java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ImportRuntimeHints;

@SpringBootApplication
@ImportRuntimeHints(MyRuntimeHints.class)
public class MyApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }

}
The GraalVM native image tracing agent can help you discover missing hints automatically. Run your application with -agentlib:native-image-agent=config-output-dir=/path/to/config-dir/ and exercise the code paths you want to cover. Copy the generated files to src/main/resources/META-INF/native-image/ and the compiler will incorporate them.

Provide resource hints

Resources loaded from the classpath at runtime must be declared explicitly for native images. Register them through the RuntimeHints API inside your RuntimeHintsRegistrar:
// Register a single resource by name
hints.resources().registerPattern("my-resource.txt");

// Register all files in a directory
hints.resources().registerPattern("config/*");
Spring Boot automatically registers hints for standard Spring resources such as application.properties, Hibernate mapping files, and Spring XML configuration files. You only need to register resources that your own code loads dynamically.

Use @RegisterReflectionForBinding for JSON serialization

When you serialize or deserialize types through RestTemplate, RestClient, or WebClient rather than through a @RestController return type, Spring cannot infer the required reflection hints automatically. Annotate the bean that performs the call with @RegisterReflectionForBinding:
MyService.java
import org.springframework.aot.hint.annotation.RegisterReflectionForBinding;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClient;

@Service
@RegisterReflectionForBinding(MyDto.class)
public class MyService {

    private final RestClient restClient;

    public MyService(RestClient.Builder builder) {
        this.restClient = builder.baseUrl("https://api.example.com").build();
    }

    public MyDto fetchData() {
        return restClient.get()
                .uri("/data")
                .retrieve()
                .body(MyDto.class);
    }

}

Test AOT-processed applications on the JVM

Compiling a native image takes several minutes. Before doing a full native build, validate your AOT-generated code on the JVM by enabling AOT mode at startup:
java -Dspring.aot.enabled=true -jar target/myapp-0.0.1-SNAPSHOT.jar
The JAR you test must include AOT-generated assets. Build with ./mvnw -Pnative package (Maven) or ensure the org.graalvm.buildtools.native plugin is applied (Gradle) so that AOT processing runs during the build.
If the application starts successfully with spring.aot.enabled=true, you can have high confidence it will work as a native image. You can then run your regular integration tests against the running JVM process before committing to a full native compile.

Test in native image mode

GraalVM Native Build Tools supports running your entire test suite inside a native image. This is slower than a JVM test run, but catches any hint gaps that only appear in native mode. Use it in CI pipelines rather than in inner-loop development.
The spring-boot-starter-parent defines a nativeTest profile. Activate it to build and run tests natively:
./mvnw -PnativeTest test
Most developers run nativeTest only in CI. During local development, run tests on the JVM and use spring.aot.enabled=true to validate AOT compatibility cheaply.

Debug AOT hint issues

Test that your RuntimeHintsRegistrar registers the hints you expect without running a full native build:
MyRuntimeHintsTests.java
import org.junit.jupiter.api.Test;

import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;

import static org.assertj.core.api.Assertions.assertThat;

class MyRuntimeHintsTests {

    @Test
    void shouldRegisterHints() {
        RuntimeHints hints = new RuntimeHints();
        new MyRuntimeHints().registerHints(hints, getClass().getClassLoader());
        assertThat(RuntimeHintsPredicates.resource().forResource("my-resource.txt"))
                .accepts(hints);
    }

}
Run the application with the tracing agent to capture all reflection and resource accesses at runtime:
java -Dspring.aot.enabled=true \
    -agentlib:native-image-agent=config-output-dir=/path/to/config-dir/ \
    -jar target/myapp-0.0.1-SNAPSHOT.jar
Exercise the features that failed, then stop the application. Copy the generated JSON hint files into src/main/resources/META-INF/native-image/ so GraalVM picks them up in the next build.
If a third-party library does not work in native mode, check the GraalVM reachability metadata repository before writing your own hints. Many popular libraries already have community-contributed metadata that Spring Boot includes automatically via the native-maven-plugin or org.graalvm.buildtools.native Gradle plugin.

Build docs developers (and LLMs) love