Spring Boot 4.0 & Spring Framework 7 New Features

Spring Boot 4.0 & Spring Framework 7 New Features

Spring Boot 4.0 & Spring Framework 7 New Features

In this tutorial, we are going to learn about the major updates in the Spring Framework and Spring Boot.
Spring Boot 4 and Spring Framework 7 updates might look like technical updates, but they are all about building software faster, cleaner, and safer. We will go through major updates one by one.

Modular Auto-Configuration

In Spring Boot 3, the auto-configuration was like packaging the giant suitcase for a short weekend trip. Even if you need to build a lightweight API, the app will inherit auto-configuration for unused modules.
So, in Spring Boot 4, auto-configuration is split into smaller modules. If you import the webmvc dependency, the app will only load the webmvc configuration, resulting in faster startup with less memory usage.

Native API Versioning support

When we update the REST API, older mobile apps might break until they are forced to update the app to the latest version.
In Spring Boot 3, we had to write custom URL mappings to support versioning, e.g /api/v1/orders . If we update the API, this will change to /api/v2/orders .
Spring Framework 7 introduces built-in annotation-based API versioning. Let's look into the sample example.
java
package com.csbyte.taskpulse.newfeatures;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
// @RequestMapping("/api/{version}/orders") // Enable path versioning
@RequestMapping("/api/orders") 
public class OrderController {
    // Version 1 of the API (Legacy apps)
    @GetMapping(path = "/{id}", version = "1") // Enable header versioning
    public OrderV1 getOrderV1(@PathVariable String id) {
        return new OrderV1(id, "Processing");
    }

    // Version 2 of the API (Modern apps)
    @GetMapping(path = "/{id}", version = "2")
    public OrderV2 getOrderV2(@PathVariable String id) {
        return new OrderV2(id, "IN_TRANSIT", "Driver arriving in 5 mins");
    }
}
For a new application, we can use header versioning, or if you want to use the old style, simply use path versioning.

Type-Safe Interface HTTP Clients

Previously, we used RestTemplate or WebClient, or pulled in Spring Cloud OpenFeign. In Spring Boot 4, we define a plain Java interface with @HttpExchange annotation, and Spring will create the HTTP call logic.
java
package com.csbyte.taskpulse.newfeatures;

import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;

import java.util.Map;

// Spring Boot 4 auto-configures HTTP Interface clients cleanly
@HttpExchange("/external/notifications")
public interface NotificationClient {

    @PostExchange("/send")
    Map<String, String> sendNotification(@RequestBody Map<String, String> payload);

    @GetExchange("/status/{id}")
    Map<String, String> getDeliveryStatus(@PathVariable("id") String notificationId);
}

JSpecify Null-Safety Annotations

Spring Boot 4 adopts JSpecify annotations in the framework. Annotating the classes with null marks allows the IDE and compiler to catch the exception before the code runs.
java
package com.csbyte.taskpulse.newfeatures;

import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
import org.springframework.stereotype.Service;

@Service
public class UserService {
    
    // Return value can be null; callers must check before accessing methods
    public @Nullable User findUserById(String id) {
        return null;
    }

    // Parameter must not be null; IDE flag compile-time errors if null is passed
    public void registerUser(@NonNull User user) {
        // Business logic
    }
}

Jackson 3 Package Migration

Spring Boot 4 uses Jackson 3 as the default JSON library. Package change from com.fasterxml.jackson to tools.jackson .

Built-in Resilience

Spring Framework 7 moves the fault tolerance to spring-core . Instead of depending on the external library Resilience4j or others, we can simply use the native retrying mechanism(@Retryable & @ConcurrencyLimit).
java
package com.csbyte.taskpulse.newfeatures;

import org.springframework.resilience.annotation.Retryable;
import org.springframework.stereotype.Service;

@Service
public class PaymentGatewayService {
    
    // Automatically retries 3 times if the payment time out
    @Retryable(includes = PaymentTimeOutException.class, maxRetries = 3, delay = 1000)
    public PaymentResponse processPayment(PaymentRequest request) {
        return callRemotePaymentServer(request);
    }

    // Limits the execution to 5 concurrent call. Useful when utilizing virtual thread
    public AccountBalance fetchBalance(String accountId) {
        return callLegacyDatabase(accountId);
    }
}

Programmatic Bean Registration

What if you want to register the beans conditionally? Instead of defining beans inside configuration classes using the @Bean annotation, Spring 7 provides a way to register the bean conditionally using BeanRegistrar .
java
package com.csbyte.taskpulse.newfeatures;

import org.springframework.beans.factory.BeanRegistrar;
import org.springframework.beans.factory.BeanRegistry;
import org.springframework.core.env.Environment;

public class FeatureToggleBeanRegistrar implements BeanRegistrar {
    @Override
    public void register(BeanRegistry registry, Environment env) {
        boolean isProduction = env.getProperty("app.live", Boolean.class, false);
        if (isProduction){
            registry.registerBean("paymentGateway", PaymentGateway.class);
        }else {
            registry.registerBean("paymentGateway", SandBoxPaymentGateway.class);
        }
    }
}

Modern Messaging (JmsClient)

Like RestClient and JdbcClient, Spring 4 introduces JmsClient in place of JmsTemplate for message brokers.
java
package com.csbyte.taskpulse.newfeatures;

import org.springframework.jms.core.JmsClient;
import org.springframework.stereotype.Component;

@Component
public class OrderNotificationSender {

    private final JmsClient jmsClient;

    public OrderNotificationSender(JmsClient.Builder builder) {
        this.jmsClient = builder.build();
    }

    public void sendOrderConfirmation(OrderEvent event) {
        jmsClient.destination("order-queue")
                 .payload(event)
                 .property("priority", "HIGH")
                 .send();
    }
}

Renamed Starters

spring-boot-starter-web is now spring-boot-starter-webmvc . spring-boot-starter-aop is now spring-boot-starter-aspectj .
In Spring Boot 3:
  • spring-boot-starter-web (Monolithic starter)
In Spring Boot 4:
  • spring-boot-starter-webmvc (Servlet Spring MVC)
  • spring-boot-starter-webflux (Reactive WebFlux)
  • spring-boot-starter-aspectj (AspectJ AOP)

Testing Upgrades

MockitoBean

Legacy @MockBean and @SpyBean are removed and replaced by @MockitoBean and @MockitoSpyBean .
@SpringBootTest no longer auto-configures MockMvc or TestRestTemplate we need to annotate with @AutoConfigureMockMvc .
java
package com.csbyte.taskpulse.newfeatures;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import static org.mockito.BDDMockito.given;

@SpringBootTest
@AutoConfigureMockMvc // Explicitly opt in to MockMvc setup
class OrderServiceTest {

    @MockitoBean // Modern Spring 7 Mockito annotation
    private PaymentProcessor paymentProcessor;

    @Test
    void testPayment() {
        given(paymentProcessor.process()).willReturn(true);
    }
}

RestTestClient

RestTestClient is introduced in Spring Boot 4 to test web controllers and REST APIs.
java
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.client.RestTestClient;

@SpringBootTest
@AutoConfigureMockMvc // Explicitly enables web test setup in Spring Boot 4
class OrderControllerTest {

    @Autowired
    private RestTestClient restTestClient;

    @Test
    void shouldReturnOrderDetails() {
        // Fluent, readable HTTP request construction and verification
        restTestClient.get()
                .uri("/api/orders/101")
                .accept(MediaType.APPLICATION_JSON)
                .exchange() // Executes the HTTP call
                .expectStatus().isOk()
                .expectBody()
                .jsonPath("$.id").isEqualTo("101")
                .jsonPath("$.status").isEqualTo("COMPLETED");
    }
}

Messaging, Batch, & Observability

  • spring-boot-starter-batch run in-memory by default. Add spring-boot-starter-batch-jdbc for a persistent database.
  • Introduces spring-boot-starter-opentelemetry to export metrics and traces
  • Default Health like /actuator/health/liveness and /actuator/health/readiness endpoints are on by default.
In this tutorial, we learn how Spring Boot 4 and Spring Framework 7 help modernize our Java stack with cleaner code, faster startup times, and better performance.