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-batchrun in-memory by default. Addspring-boot-starter-batch-jdbcfor a persistent database.- Introduces
spring-boot-starter-opentelemetryto export metrics and traces - Default Health like
/actuator/health/livenessand/actuator/health/readinessendpoints 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.
