Beyond Startup Time: The Real Cost Metrics in Spring Boot 2026
Learn why measuring only startup time in Spring Boot 2026 is misleading. A reproducible lab reveals real trade-offs between JVM, CDS, AOT, and GraalVM native.
When developers discuss Spring Boot performance in 2026, the first question is almost always about startup time. It's a tempting shortcut — a single number that seems to indicate efficiency. However, relying on startup time alone leads to incomplete architectural decisions. A reproducible lab, JuanTorchia/springboot-jvm-2026 (tag editorial-final-startup-matrix), demonstrates that focusing only on startup metrics ignores critical production costs like JIT warm-up, resource consumption, and behavior under load. This article explores the lab's findings through a Q&A format, revealing why a holistic view is essential for modern Spring Boot applications.
1. Why is measuring only startup time a trap in Spring Boot 2026?
Startup time is the first metric thrown on screen, but it's a poor proxy for production performance. A quick boot doesn't guarantee low latency under sustained load because Java's JIT compiler needs time to optimize hot paths. In the lab, endpoints doing real work — validation, database queries, CRC32 calculations — show that differences between JVM, CDS, AOT-JVM, and GraalVM native emerge only after the JIT has enough data. A naive benchmark using a simple GET /ping would make native look superior, yet ignore that a warmed-up JVM can match or exceed native throughput. Additionally, startup time ignores operational costs like memory footprint, build time, and debugging complexity. Learn how the lab models real workloads.

2. What does the reproducible lab demonstrate?
The lab's backend is not a hello-world; it exposes three endpoints with real responsibilities: POST /api/orders (Jakarta Validation on a record), GET /api/orders/{id} (Spring Data JDBC over PostgreSQL 17), and POST /api/work (deterministic CPU work using CRC32 with up to 5,000 iterations). Flyway runs migrations, Actuator provides readiness/liveness probes, and HikariCP is configured explicitly. The lab compares four modes: plain JVM, JVM with AppCDS, Spring Boot AOT on JVM, and GraalVM native image. Without the /api/work endpoint, JVM and native look nearly identical under warm latency because JIT has nothing to optimize. This design forces the JIT to work, revealing real trade-offs. See how WorkService works.
3. How does the WorkService endpoint force differences between JVM and native?
WorkService.calculateScore(String input, int iterations) performs a deterministic loop: it takes the input bytes, initializes a CRC32, and in each iteration rotates bits with a Fibonacci constant. The 5,000 iteration cap is not arbitrary — it was validated with WorkServiceTest to stay predictable without becoming a throughput test. This endpoint mixes real CPU work with a database query (countOrders()). On plain JVM, the JIT compiler gradually optimizes the loop, reducing latency over time. On GraalVM native, there's no JIT, so the latency is flat from the start. This creates a clear divergence: fast startup but no warm-up improvement for native, versus slower startup but potential warmth gains for JVM. That's why measuring only startup misses the operational picture. Explore the four modes compared.

4. What are the four operating modes compared in the lab?
The lab benchmarks four configurations:
- jvm —
java -jaron Eclipse Temurin 21, the traditional baseline. - cds — JVM with a dynamic AppCDS archive prepared in a separate phase.
- aot-jvm — Spring Boot AOT on JVM with
-Dspring.aot.enabled=trueverified inside the container. - native — GraalVM Native Image built using
ghcr.io/graalvm/native-image-community:21.
Each mode changes the startup behavior and runtime characteristics. For instance, aot-jvm improves startup but still benefits from JIT later. The native image starts fastest but has no JIT to adapt to load. Learn about the AOT-JVM flag issue.
5. What issue was discovered with the AOT-JVM mode during testing?
In the editorial run of May 17, 2026 (17:31–17:44 Buenos Aires time), the aot-jvm results initially made no sense. The measured startup times were inconsistent until the team confirmed that the -Dspring.aot.enabled=true flag was actually reaching the container. Without that flag, the JVM ran in standard mode, not with AOT optimizations. This highlights a critical operational lesson: configuration drift is real, and runtime flags must be verified in the actual deployment environment. The lab's reproducibility relies on scripted container builds and explicit checks — a practice every team should adopt to avoid misleading benchmarks. Return to the startup trap.