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
This commit is contained in:
Claude
2026-09-20 11:03:09 +00:00
parent e4b5636f7c
commit 8cdfcd4d8d
23 changed files with 627 additions and 0 deletions
@@ -0,0 +1,53 @@
# 02 — AOT processing, and why `package` alone doesn't build the image
[← Previous: 01 — versions and setup](01-versions-and-setup.md) | [Back to README →](../README.md) | [Next: 03 — the reflection trap →](03-the-reflection-trap.md)
## 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`](../src/main/java/com/ankurm/graalvmdemo/HelloController.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](output/06-package-does-not-compile-native.txt)
and [docs/output/02-native-build.txt](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](output/02-native-build.txt).