If you’ve been following my blog, you know I love a good migration story. Whether it’s moving to TanStack Start or refining shadcn/ui forms, the goal is always the same: better developer experience and more robust code.
Today, we’re looking at the big one. Spring Boot 4.0 is officially out, and it’s arguably the most important release since 3.0. It moves the baseline to &Java 17 (with a massive push for Java 25), adopts Jakarta EE 11, and introduces features that finally kill off years of boilerplate.
Let’s look at exactly what changed and how your code will look before and after the upgrade.
1. Native API Versioning
For years, versioning an API in Spring meant custom URL paths, header filters, or complex RequestCondition hacks. Spring Boot 4 brings this into the core framework.
The Spring Boot 3 Way (Manual Pathing)
// You had to manually manage the path segments
@RestController
@RequestMapping("/api/v1/orders")
public class OrderControllerV1 { ... }
@RestController
@RequestMapping("/api/v2/orders")
public class OrderControllerV2 { ... }
The Spring Boot 4 Way (Native Mapping)
Now, versioning is a first-class citizen. You can keep the path clean and let Spring handle the routing logic via headers, query params, or path segments.
@RestController
@RequestMapping(path = "/orders")
public class OrderController {
@GetMapping(version = "1")
public List<OrderV1> getOrdersV1() { ... }
@GetMapping(version = "2")
public List<OrderV2> getOrdersV2() { ... }
}
Pro Tip: Configure the strategy in your application.properties:
spring.mvc.apiversion.use.header=X-API-Version
spring.mvc.apiversion.default=1
2. Declarative Resilience (No More Spring Retry)
In the Spring Boot 3 era, you usually had to pull in the spring-retry dependency and use @EnableRetry. In Spring Boot 4, basic resilience patterns like Retry and Concurrency Limits are built directly into spring-context.
Spring Boot 3 (Requires External Dependency)
// Requires @EnableRetry in your config
@Service
public class StockService {
@Retryable(value = {NetworkException.class}, maxAttempts = 2)
public void updateStock() { ... }
}
Spring Boot 4 (Native & Robust)
The new native @Retryable and @ConcurrencyLimit (Bulkhead pattern) work out of the box with the new spring-boot-starter-resilience.
@Service
public class StockService {
@Retryable(includes = NetworkException.class, maxRetries = 3)
@ConcurrencyLimit(limit = 5) // Native Bulkhead pattern!
public void updateStock() {
// Automatically retries and limits concurrent threads
}
}
3. Testing with RestTestClient
Spring Framework 7 introduces RestTestClient, which effectively replaces the choice between MockMvc and WebTestClient for most scenarios. It provides a single, fluent API that works for both mock environments and real running servers.
Spring Boot 3 (Choice of Two APIs)
// MockMvc for internal tests or WebTestClient for reactive/live
@Autowired
private MockMvc mockMvc;
mockMvc.perform(get("/hello"))
.andExpect(status().isOk());
Spring Boot 4 (Unified Fluent API)
@Autowired
private RestTestClient client;
@Test
void testHello() {
client.get().uri("/hello")
.exchange()
.expectStatus().isOk()
.expectBody(String.class).isEqualTo("Hello World");
}
4. Architectural Modularization
Spring Boot 4 has undergone a "diet." The internal spring-boot-autoconfigure JAR has been split into dozens of smaller, technology-specific modules.
| Feature | Spring Boot 3 | Spring Boot 4 |
|---|---|---|
| Auto-config | Monolithic autoconfigure.jar | Small, focused modules (e.g., spring-boot-autoconfigure-data-jpa) |
| AOT / Native | Basic GraalVM support | Optimized GraalVM 25 support with lower memory footprint |
| Startup | Slower due to classpath scanning | Faster build/boot via modular hints |
If you notice missing auto-configurations after upgrading, you may need to add a specific starter for that technology (e.g., spring-boot-starter-flyway instead of just the Flyway core JAR).
5. Type Safety: The JSpecify Shift
Null safety is no longer a "suggestion." Spring Boot 4 fully adopts JSpecify. This isn't just a new set of annotations; it changes how Kotlin and static analysis tools view your code.
- Spring Boot 3: Used a mix of
@Nullablefrom various packages (JSR-305, JetBrains). - Spring Boot 4: Standardizes on
org.jspecify.annotations.
Final Thoughts: The Road to Java 25
While the baseline is Java 17, the real magic of Spring Boot 4 happens on Java 25. With virtual threads enabled (spring.threads.virtual.enabled=true), the framework optimizes internal task executors to use lightweight threads, making your blocking code perform like reactive code without the complexity.
Ready to migrate?
- Move to Spring Boot 3.5 first - if you are not already there.
- Fix all deprecation warnings (especially around
RestTemplateand old Actuator properties). - Flip the switch to Spring Boot 4.0 and enjoy a leaner, safer backend.
What’s your favorite new feature? I’m personally voting for native API versioning—it's about five years overdue! Let me know in the comments.
Originally posted on my blog - Memory Leak
Top comments (0)