What is New in Spring Boot 4.0 and Spring Framework 7: Migration Guide and New Features

Spring Boot 4.0 and Spring Framework 7 represent a significant leap forward in modern Java development. The focus on cloud-native patterns, improved developer experience, and stronger language features makes these releases essential upgrades.

November 18, 2025
9 min read
What is New in Spring Boot 4.0 and Spring Framework 7: Migration Guide and New Features

If you're a Java developer like me, you've probably noticed how Spring Boot has become the go-to framework for building modern Java applications. With Spring Boot 4.0 and Spring Framework 7 which arrived in November 2025, we're standing at another major milestone in this journey.

Spring Boot 4 and Spring Framework 7 aren't just regular updates. They're transformative releases that will reshape how we build, deploy, and maintain Java applications. In this article, we are trying to understand what's changing, why it matters, and how you can prepare for this transition.


What is Changing at the Platform Level

Java Version: Time to Move Forward

Let's start with the elephant in the room—Java versions.

  • Minimum requirement: Java 17 (still holding strong)
  • Recommended version: Java 25 (the latest LTS release coming in September 2025)

What does this mean? If you're still running Java 11 or 8, it's time to seriously plan your upgrade. Java 17 brings features like records, sealed classes, and strong pattern matching that can significantly improve your code quality. Java 25 takes this further with advanced performance optimizations and new APIs that Spring 7 is designed to take advantage of.

Simple tip: Start testing your applications with Java 21 LTS right now. It's a safe middle ground and will prepare you for Java 25 migration.

Jakarta EE 11: The "Jakarta is Here" Moment

Remember when Spring Boot 3 migrated from javax.* to jakarta.* packages? This time, Spring Framework 7 fully embraces Jakarta EE 11 with deeper integration:

  • Servlet 6.1 with modern web APIs
  • JPA 3.2 and Hibernate ORM 7.0 for better database operations
  • Bean Validation 3.1 with improved support for Kotlin records

If you haven't migrated from javax to jakarta yet, Spring Boot 4 won't work for you. This is a non-negotiable requirement.


Spring Framework 7 - The Developer Experience Revolution

Feature 1: Built-in Resilience Without Extra Dependencies

The Problem: Previously, you needed the spring-retry external project to add retry capabilities to your application.

The Solution: Spring Framework 7 brings resilience features directly into the core framework.

Working with @Retryable

@Service
public class PaymentService {

    @Retryable(maxAttempts = 3, backoff = @Backoff(delay = 1000))
    public TransactionResult processPayment(Payment payment) {
        // Call external payment gateway
        return externalGateway.charge(payment);
    }

    @Recover
    public TransactionResult recover(RuntimeException ex, Payment payment) {
        logger.error("Payment failed after retries for: " + payment.getId());
        return new TransactionResult(false, "Payment failed");
    }
}

Notice how you don't need any external project for this? Just use the annotation, configure your retry policy, and Spring handles the rest. The @Recover method runs when all retries are exhausted.

Controlling Concurrency with @ConcurrencyLimit

@Service
public class ReportGenerationService {

    @ConcurrencyLimit(value = 5)
    public Report generateDetailedReport(ReportQuery query) {
        // This method will never run more than 5 times concurrently
        // Other requests will queue up
        return complexReportCalculation(query);
    }
}

Imagine you have a report generation service that's very resource-intensive. Without @ConcurrencyLimit, 100 simultaneous requests would crash your server. With this annotation, only 5 can run at the same time, and the rest wait gracefully.

How to enable these features:

@Configuration
@EnableResilientMethods
public class ResilienceConfig {
    // Your other beans here
}

Feature 2: API Versioning Without Hacks

The Old Way: You had to create separate endpoints for each API version or use complex URL patterns.

The New Way: Spring Framework 7 lets you define API versions declaratively.

@RestController
@RequestMapping("/api/users")
public class UserController {

    @GetMapping
    @RequestMapping(produces = "application/vnd.api+json;version=1")
    public List<UserV1> getUsersV1() {
        // Version 1 returns limited fields
        return userService.getUsersV1();
    }

    @GetMapping
    @RequestMapping(produces = "application/vnd.api+json;version=2")
    public List<UserV2> getUsersV2() {
        // Version 2 returns enhanced data
        return userService.getUsersV2();
    }
}

The framework automatically routes requests to the correct version based on media type. This makes versioning explicit and maintainable.

Feature 3: Programmatic Bean Registration for Dynamic Applications

Use Case: Your application needs to register beans based on configuration files or external systems at runtime.

@Configuration
public class DynamicBeanConfiguration implements BeanRegistrar {

    @Override
    public void registerBeans(BeanRegistrationContext context) {
        ConfigurationService config = context.getBeanFactory()
            .getBean(ConfigurationService.class);

        config.getDataSources().forEach(datasource -> {
            context.registerBean(
                "datasource_" + datasource.getName(),
                DataSource.class,
                () -> createDataSource(datasource)
            );
        });
    }

    private DataSource createDataSource(DatasourceConfig config) {
        // Create datasource based on configuration
        return new DynamicDataSource(config);
    }
}

Instead of defining all beans statically in XML or annotations, you can now create them dynamically. This is incredibly useful for multi-tenant applications or plugin-based architectures.

Feature 4: Better Null Safety with JSpecify

Spring Framework 7 now uses JSpecify annotations for improved null safety across the framework. This means:

@Service
public class CustomerService {

    public @Nullable Customer findCustomer(@NonNull String customerId) {
        // This method promises:
        // - It will never accept a null customerId
        // - It might return null (hence @Nullable)
        return customerRepository.findById(customerId).orElse(null);
    }
}

IDEs and static analysis tools can now detect potential null pointer exceptions before runtime. It's like having an extra pair of eyes watching your code.

Feature 5: Optional Support in Spring Expression Language (SpEL)

Before: Handling Optional values in SpEL was awkward.

After: Spring Framework 7 treats Optional naturally:

@Service
public class NotificationService {

    @Value("#{userService.findUserById(#userId)?.name ?: 'Guest User'}")
    private String userName;

    public void sendNotification(String userId, String message) {
        // The Elvis operator (?:) provides a default
        // The safe navigation (?.) prevents null exceptions
        notificationCenter.send(userName, message);
    }
}

If the Optional is empty, it gracefully falls back to "Guest User" without throwing exceptions.

Feature 6: Enhanced HTTP Clients with @ImportHttpServices

The Scenario: Your microservice talks to 5 different external APIs. Managing all these clients is messy.

@Configuration
@ImportHttpServices(
    group = "external-services",
    types = {WeatherApiClient.class, EmailServiceClient.class, PaymentApiClient.class}
)
public class HttpClientConfiguration {
    // Spring automatically creates proxy beans for these interfaces
}

// Define your HTTP clients as interfaces
@HttpService(name = "weather-api", url = "https://api.weather.service")
public interface WeatherApiClient {

    @GetExchange("/forecast/{city}")
    WeatherForecast getForecast(@PathVariable String city);
}

// Use them like regular Spring beans
@Service
public class WeatherAnalysisService {

    private final WeatherApiClient weatherClient;

    public WeatherAnalysisService(WeatherApiClient weatherClient) {
        this.weatherClient = weatherClient;
    }

    public void analyzeTrends(String city) {
        WeatherForecast forecast = weatherClient.getForecast(city);
        // Process forecast
    }
}

Spring automatically converts these interfaces into fully-functional HTTP clients. No more boilerplate RestTemplate or WebClient setup code!

Feature 7: Stream Processing in HTTP Clients

Working with large files or streaming data is now easier:

@HttpService(url = "https://data-service.com")
public interface DataStreamClient {

    @GetExchange(value = "/download/large-dataset", produces = "application/octet-stream")
    InputStream downloadLargeFile();

    @PostExchange(value = "/upload/stream", consumes = "application/octet-stream")
    void uploadLargeFile(OutputStream outputStream);
}

Perfect for handling large file uploads and downloads without loading everything into memory.

Feature 8: JdbcClient and JmsClient Enhancements

Spring Framework 7 introduces fluent APIs for database and messaging operations:

@Service
public class OrderRepository {

    private final JdbcClient jdbcClient;

    public List<Order> findOrdersByCustomer(String customerId) {
        return jdbcClient.sql(
            "SELECT * FROM orders WHERE customer_id = ? ORDER BY created_at DESC"
        )
        .param(1, customerId)
        .map((rs, rowNum) -> new Order(
            rs.getString("id"),
            rs.getString("status"),
            rs.getTimestamp("created_at").toLocalDateTime()
        ))
        .list();
    }
}

@Service
public class NotificationPublisher {

    private final JmsClient jmsClient;

    public void publishOrderNotification(Order order) {
        jmsClient.send("notification-queue")
            .priority(9)
            .correlationId(order.getId())
            .body(new OrderNotificationMessage(order))
            .send();
    }
}

No more verbose JdbcTemplate or MessagingTemplate code. Everything is fluent and readable.

Feature 9: Centralized HTTP Message Converter Configuration

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void configureMessageConverters(
        HttpMessageConverters.ServerBuilder builder) {

        builder.jsonMessageConverter(
            new JacksonJsonHttpMessageConverter(
                JsonMapper.builder()
                    .featuresToEnable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
                    .featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
                    .build()
            )
        );
    }
}

Manage all serialization and deserialization logic in one place. This ensures consistency across your entire application.

Feature 10: RestTestClient for Testing REST APIs

Testing REST endpoints is now cleaner:

@WebMvcTest(OrderController.class)
public class OrderControllerTest {

    @Autowired
    private RestTestClient restTestClient;

    @Test
    public void testGetOrderDetails() {
        restTestClient.get()
            .uri("/api/orders/{id}", "ORDER-123")
            .exchange()
            .expectStatus().isOk()
            .expectBody(Order.class)
            .isEqualTo(expectedOrder);
    }
}

The API is fluent, type-safe, and mirrors the RestTemplate API that developers already know.

Feature 11: Improved Path Matching Patterns

@RestController
public class DocumentController {

    // Matches: /files/documents/guide.pdf or /files/docs/readme.txt
    @GetMapping("/**/docs/{fileName}")
    public ResponseEntity<byte[]> getDocument(@PathVariable String fileName) {
        return ResponseEntity.ok(fileService.getContent(fileName));
    }

    // Matrix variables also work better now
    @GetMapping("/search/{query}")
    public List<SearchResult> search(
        @PathVariable String query,
        @MatrixVariable Map<String, String> filters) {
        return searchService.execute(query, filters);
    }
}

More powerful and expressive routing without the complexity of regex patterns.


Breaking Changes and Migration Path

What's Being Removed

Deprecated FeatureWhyAlternative
javax.annotation.*Migrated to Jakarta EEUse jakarta.annotation.*
javax.inject.*Migrated to Jakarta EEUse jakarta.inject.*
spring-jcl moduleSimplified loggingUse Apache Commons Logging directly
XML-based MVC configPromotes modern practicesUse WebMvcConfigurer interfaces
JUnit 4Framework is EOLMigrate to JUnit 5
Jackson 2.xPerformance improvementsUpgrade to Jackson 3.x
Suffix pattern matchingReduced complexityUse explicit media types
Undertow servlet containerResource optimizationUse Tomcat, Jetty, or Netty

Step-by-Step Migration Checklist

  • Step 1: Upgrade to Java 17+ (test with Java 21 LTS)
  • Step 2: Update all javax.* imports to jakarta.*
  • Step 3: Update your test framework to JUnit 5
  • Step 4: Replace XML configurations with Java-based @Configuration classes
  • Step 5: Update Jackson dependencies to version 3.x
  • Step 6: Run your entire test suite
  • Step 7: Test native image builds if you use GraalVM
  • Step 8: Update documentation and deployment processes

Conclusion

Spring Boot 4.0 and Spring Framework 7 represent a significant leap forward in modern Java development. The focus on cloud-native patterns, improved developer experience, and stronger language features makes these releases essential upgrades.

The Java ecosystem isn't standing still, and neither should we. Spring Boot 4 and Spring Framework 7 are proof that Java continues to evolve with the industry's needs. Embrace the change, and your future applications will be faster, more maintainable, and easier to deploy in modern cloud environments.