native-image
- Measuring OpenTelemetry Overhead Without Fooling Yourself
What OpenTelemetry costs a Spring Boot app in MiB and CPU per order, and the proofs each test cell needed before its number counted.
- Your GraalVM Version Is the Other Number
Which GraalVM to build native images with in 2026, what the free edition lacks, and why the image tag shows the JDK version, not GraalVM's.
- The Config I Measured Was Not the Config That Ran
A Spring Boot native image never got its telemetry config file, and optional: kept quiet. Plus a compose run that swapped the app before a load test.
- Counts Match, Dashboards Don't: OTel Agent vs Starter
Swapping the OTel Java agent for a starter, which native requires, kept every exact count. Names, units, labels and buckets moved. What breaks, per target.
- Java 25 Changes Your Telemetry in Strings, Not Code
A 21 to 25 move compiles fine. Then ZGC series rename, pinning metrics go quiet, -javaagent sidelines the AOT cache, and going native on top drops GC metrics.
- Same Builder, Different Binary
One GraalVM builder image, one banner on every build log, 18 native binaries. Twelve came out PIE and six didn't, and nobody had touched a linker option.
- The Flag Was Verified. The File Was Never Read.
Spring's process-aot writes its own native-image.properties over yours. A verified GraalVM flag sat in the repo for 43 days and never reached the build.
- Health 200, API 200, Prometheus 000
A GraalVM 25i4 bump made native /prometheus stop answering. The reflection gap was old; a new default turned a missing metric into a hang.
- The Baseline Was the Bug
The JVM platform-thread baseline was measuring virtual threads, and native's famous 8x was dispatch plus double the heap. Matched, the gap is memory.
- Native Image Grew to 734 MB
Five things the benchmark taught me that the documentation didn't mention.