Spring Boot 3.2 on GraalVM: When It's Worth It
Native images promise faster startup. The trade-offs are real.
I shipped a Spring Boot service compiled to a native image at TD Bank. Cold-start went from 8 seconds to 80 milliseconds. The build time went from 30 seconds to 6 minutes. Whether that's a win depends entirely on your shape of load.
GraalVM native images for Spring Boot 3 are not new, but they're newly practical. Here's what I learned shipping one in production.
The win
A standard Spring Boot service starts in 6-12 seconds depending on what's in your application context. Compiled to a native image, the same service starts in 50-200ms.
Memory footprint also drops by ~3x. A service that needed 512MB of heap on JVM runs comfortably in 180MB native.
The cost
Native compilation is slow. My 30-second mvn package becomes a 6-minute mvn -Pnative native:compile. That's a CI bottleneck if you compile on every push.
Worse, native images give up runtime class loading. Frameworks that lean on reflection (Spring is one) need to be told ahead of time what to load. Spring Boot 3 ships with a lot of this metadata baked in, but the moment you use a less-common library, you're writing reachability metadata yourself.
I had a Jackson configuration in my service that worked fine on JVM and broke silently on native. Took me an hour to track down. The pattern: assume any library that uses dynamic proxies, reflection, or runtime bytecode generation needs hints.
When it's worth it
- Lambda / FaaS. Cold-start is the entire reason you'd consider this. AWS Lambda billing rewards fast starts.
- CLI tools. If you have a Spring-based CLI, native image transforms the user experience.
- Heavy auto-scaling. If you spawn 100s of pods that each handle a small batch of work, the cumulative startup-cost savings are real.
When it's NOT worth it
- Long-running monoliths. Your container starts once. Trading 30 seconds of compile time for 8 seconds of startup time is a net loss.
- Services with rich plugin ecosystems that load classes at runtime.
- Teams that aren't ready for the operational complexity. Native debugging is harder. Heap dumps look different. Profilers behave differently. Make sure your team is willing to learn.
How I'd do it on a new project
I'd build for both targets from day one. JVM in dev, JVM in CI for tests, native in CI for the final artifact, and ship native to prod. This catches reflection-config bugs early without slowing down the inner loop.
I'd also start with Spring AOT processing on JVM first. That alone gets you 30-40% of the startup-time win without giving up reflection.
The honest verdict
Native image is the right answer for ~15% of Spring Boot deployments I see. Most teams that try it for the wrong reasons get burned. Most teams that adopt it for the right reasons love it.
Don't follow the hype either way. Profile your real-world startup latency, your real-world memory consumption, your real-world deployment cadence. Then decide.