Spring Boot 3 isn't an incremental release — it's the first version that requires Java 17, migrates to the Jakarta EE namespace, and ships a set of platform-level features that meaningfully change how you build and operate Java services. If you're still on Spring Boot 2.x, here's what's waiting on the other side and why the upgrade is worth it.
Spring Boot 3 at a glance
The baseline: Java 17+ and Jakarta EE
Spring Boot 3 dropped support for Java 8/11 and moved from the javax.* namespace to jakarta.*. This is the most disruptive part of the migration — a find-and-replace across imports — but also the one you do once and forget. The Jakarta namespace is the long-term standard; staying on javax.* indefinitely is a dead end.
Before you upgrade: audit dependencies that use javax.* directly (persistence, servlet, validation) — they need Jakarta-compatible versions.
GraalVM native compilation
Spring Boot 3 has first-class support for compiling your app to a native executable via GraalVM Native Image. The result: dramatically faster startup (milliseconds, not seconds) and much lower memory footprint — relevant for:
- Serverless/FaaS (cold starts kill you on JVM apps)
- Containers where you pay for idle memory
- CLI tools built on Spring
<!-- Add to pom.xml -->
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
</plugin>
# Build a native executable
./mvnw -Pnative native:compile
# Startup: ~50ms vs 3-8s JVM
./target/my-service
Native compilation runs at build time and requires a GraalVM JDK. Dynamic class loading and reflection need explicit hints (reflect-config.json) — Spring Boot 3 auto-generates most of these via AOT processing, but third-party libraries may need manual hints.
When to use it: serverless, CLIs, memory-constrained containers. For long-running servers where JVM warmup pays off, the JVM still has the edge on peak throughput.
Virtual threads
Spring Boot 3.2+ adds a one-property toggle to run web request handling and @Async tasks on Java 21 virtual threads:
spring:
threads:
virtual:
enabled: true
For I/O-bound services — DB queries, HTTP calls, LLM calls — this gives reactive-level concurrency with simple blocking code and no reactive rewrite. For a deep dive, see Virtual Threads in Spring Boot.