AOT and Native Image
Introduction
Spring Boot 3 supports Ahead-of-Time (AOT) processing and GraalVM Native Image to compile apps into small native binaries with fast startup and lower memory—ideal for serverless and dense containers. Trade-offs include longer build times and reflection limits. This chapter explains when native fits, runs a basic native build, and lists compatibility checks.
Prerequisites
- Spring Boot Startup Flow
- Create First Spring Boot Project
- JDK 17+ and GraalVM or Mandrel installed for native builds
JVM Jar vs Native Image
JVM java -jar | Native image | |
|---|---|---|
| Startup | Seconds (warm JVM) | Milliseconds typical |
| Memory | Higher baseline | Lower footprint |
| Build time | Fast | Slow (minutes) |
| Reflection / dynamic | Full | Needs reachability hints |
| Peak throughput | Often higher on JVM | Workload dependent |
Use native for scale-to-zero, CLI tools, edge—not default for every CRUD API until you measure.
Spring AOT Processing
Spring Boot 3 generates reflection and bean metadata at build time:
mvn -Pnative native:compileOr with Spring Boot plugin:
mvn -Pnative spring-boot:build-imageCloud Native Buildpacks produce OCI image with native binary inside—no local GraalVM required if using Paketo builder.
pom.xml profile snippet:
Follow current Spring Boot 3 docs for exact plugin versions aligned to your BOM.
Native-Compatible Dependencies
Prefer:
spring-boot-starter-web(Servlet) with supported stack- Spring Data JPA with tested drivers—verify native hints
Problematic without extra config:
- Dynamic proxies everywhere
- Unsupported libraries using heavy reflection
Class.forNameruntime loading
Run native:compile and read build-time errors—add reflect-config.json or RuntimeHints registrar when required.
RuntimeHints Example (Boot 3)
@Configuration
public class MyRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
hints.reflection().registerType(MyDto.class, MemberCategory.DECLARED_FIELDS);
}
}Register via META-INF/spring/org.springframework.aot.hint.RuntimeHintsRegistrar.
Test Native Locally
After build:
./target/demo-app
curl http://localhost:8080/helloObserve startup log timestamp vs JVM run—native should start faster.
Docker With Native Image
mvn spring-boot:build-image -Pnative -Dspring-boot.build-image.imageName=demo-native:1.0
docker run -p 8080:8080 demo-native:1.0Smaller image than full JRE layer—good for Docker deploys.
When Not to Use Native
- Heavy dynamic scripting, unknown classpath scanning
- Team lacks GraalVM build pipeline patience
- App benefits from JVM JIT peak performance on long-running batch
Start with JVM in prod; adopt native for specific services after profiling.
Tip
Validate Before Committing Architecture
Build native in CI weekly even if prod stays JVM—surfaces hint gaps early.
FAQ
native build fails on MyBatis dynamic SQL?
May need reachability metadata or stay on JVM for that service.
Smaller = always cheaper?
Dev time and CI minutes cost money too—weigh TCO.
Spring Boot 2 native?
Use Boot 3 + GraalVM 22+ toolchain.
Native + Spring Native project?
Merged into official Spring Boot 3 native support—ignore old Spring Native docs.
Windows native exe?
GraalVM builds Windows executable—Linux target common in CI/CD.
AOT without native?
spring-aot-maven-plugin processes for faster startup on JVM too—subset of benefits.