DEV Community

Cover image for Spring Boot 4: Brief Upgrade Guide and Code Comparison
Felipe Stanzani
Felipe Stanzani

Posted on

Spring Boot 4: Brief Upgrade Guide and Code Comparison

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 { ... }
Enter fullscreen mode Exit fullscreen mode

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() { ... }

}
Enter fullscreen mode Exit fullscreen mode

Pro Tip: Configure the strategy in your application.properties:

spring.mvc.apiversion.use.header=X-API-Version
spring.mvc.apiversion.default=1
Enter fullscreen mode Exit fullscreen mode

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() { ... }
}
Enter fullscreen mode Exit fullscreen mode

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
    }
}
Enter fullscreen mode Exit fullscreen mode

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());
Enter fullscreen mode Exit fullscreen mode

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");
}
Enter fullscreen mode Exit fullscreen mode

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 @Nullable from 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?

  1. Move to Spring Boot 3.5 first - if you are not already there.
  2. Fix all deprecation warnings (especially around RestTemplate and old Actuator properties).
  3. 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)