Files
spring-boot-demo/graalvm-native-images/docs/02-building-the-image.md
T
Claude 8cdfcd4d8d Add graalvm-native-images: Boot 4.1 + GraalVM CE for JDK 25, AOT processing,
the tracing agent, and a real reflection-collision trap

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EQNA6DJ9VgCtW6zhCE8Xud
2026-09-20 11:03:09 +00:00

3.3 KiB

02 — AOT processing, and why package alone doesn't build the image

← Previous: 01 — versions and setup | Back to README → | Next: 03 — the reflection trap →

What Spring's AOT processing actually generates

mvn spring-boot:process-aot (bound to the package phase automatically, no profile needed) is what makes a Spring Boot native image possible at all -- it runs the application's startup logic once, at build time, and writes down what it learns as plain Java source instead of leaving it to be rediscovered by reflection at every future startup. On this project it produced, in target/spring-aot/main/:

  • 95 generated .java source files -- one *__BeanDefinitions.java per auto-configuration class and application bean, including HelloController__BeanDefinitions.java and ReportController__BeanDefinitions.java, plus a GraalvmDemoApplication__ApplicationContextInitializer.java that builds the whole ApplicationContext as ordinary Java calls instead of classpath scanning.
  • META-INF/native-image/com.ankurm/graalvm-native-images/reachability-metadata.json -- the unified reflection/resource/proxy configuration format GraalVM now uses (a single file, not the older separate reflect-config.json / resource-config.json / proxy-config.json trio).
  • native-image.properties -- build arguments the plugin feeds straight to native-image.

Why mvn -Pnative package alone does not produce a binary

Reflected in this exact setup, not assumed: running mvn -Pnative -DskipTests package completes in under nine seconds and its log contains add-reachability-metadata, process-aot, jar, and repackage -- and nothing else. Grepping that log for native-image or Generating ' returns zero matches. mvn help:effective-pom -Pnative confirms why: the native profile this project (and a default Spring Initializr project) declares binds no execution of its own to any phase -- it only adds the plugin so its own add-reachability-metadata goal, bound in the plugin's own metadata, participates in the build. The goal that actually invokes the GraalVM compiler, native:compile, has to be run explicitly:

mvn -Pnative -DskipTests native:compile

which matches Spring's own current documentation for local (non-Docker) native builds. The ./mvnw clean package -Pnative command in tutorials and in the earlier version of this project's post does not do this -- confirmed by testing it directly, not by reading the plugin's changelog. Full transcripts of both runs, side by side: docs/output/06-package-does-not-compile-native.txt and docs/output/02-native-build.txt.

The build itself, measured

On this project's 2 vCPU / 8 GB build machine, native-image analysis found 20,126 reachable types across 29,154 fields and 91,938 methods, registered 7,495 types for reflection, and finished in 4 minutes 14 seconds to 4 minutes 19 seconds across repeated runs, peaking at 4.36 GB resident memory during compilation. The resulting executable is 93 MB, versus a 22 MB jar that still needs a JRE installed alongside it to run at all. Full transcript: docs/output/02-native-build.txt.