Spring Framework 7: What’s New, Features and Examples

Spring Framework 7 keeps Java 17 but moves to Jakarta EE 11, JSpecify null-safety and Jackson 3. This overview lists every notable change in a summary table and shows the main new features, such as @Retryable, API versioning and BeanRegistrar, with short examples.

Table comparing Spring Framework 6.2 and 7.0 baselines for Java, Jakarta EE, Servlet, JPA, Bean Validation, Kotlin, JUnit, Jackson, null-safety, logging and Undertow

Spring Framework 7 is the seventh major version of the Spring core framework, with new features such as built-in retry and API versioning. Version 7.0 was released in November 2025. It still runs on Java 17, and Java 25 is the recommended version.

We upgrade to Spring Framework 7 when we move a Spring Boot application to Spring Boot 4, because Spring Boot 4.0 and 4.1 are built on it. Spring applications without Spring Boot move to it from Spring Framework 6.2, whose open source support ended in June 2026.

The following example uses Spring Framework 7.0.9 (the version Spring Boot 4.1.1 brings) on Java 25, without Spring Boot. A BeanRegistrar registers the beans in Java code, and @Retryable calls a failing method again. The complete project with tests for every snippet is on GitHub.

@Configuration
@EnableResilientMethods                       // enables @Retryable and @ConcurrencyLimit
@Import(LibraryRegistrar.class)               // a BeanRegistrar registers the beans
public class LibraryConfig {
}

try (var context = new AnnotationConfigApplicationContext(LibraryConfig.class)) {
  ShelfClient shelf = context.getBean(ShelfClient.class);
  int copies = shelf.copies("dune");          // 3, after 2 failed calls retried by @Retryable
}

Notice that we add no Spring Retry or Resilience4j dependency, because retry is built into Spring 7.

The summary table lists every notable change, and the sections after it show each change with a short example.

1. Spring Framework 7 Changes at a Glance

Spring Framework 7 needs newer library versions, adds new APIs and removes most code that 6.x deprecated. The type column shows whether a change needs upgrade work (Baseline, Changed, Removed) or is optional (New). Baseline means a new minimum version.

ChangeTypeWhat it doesMore
Java 17 minimum, Java 25 recommendedBaselineSame minimum as 6.xsection 2
Jakarta EE 11, Kotlin 2.2, JUnit 6BaselineServlet 6.1 and JPA 3.2; Tomcat 11.0 or Jetty 12.1section 2
JSpecify null-safetyNewSpring APIs say which values can be nullsection 3.1
API versioningNewPicks the controller method by request versionsection 3.2
HTTP service groupsNew@ImportHttpServices creates HTTP clients by groupsection 3.3
@Retryable, @ConcurrencyLimitNewRetry and parallel-call limits without Spring Retrysection 3.4
BeanRegistrarNewRegisters beans in code with loops and if checkssection 3.5
Optional in SpELNew?. and ?: treat an empty Optional like nullsection 3.6
JmsClientNewSends and receives JMS messages, like JdbcClientsection 3.7
Jackson 3, HttpMessageConvertersChangedJackson 3 by default; converters set up in one placesection 3.8
CGLIB proxies by default, @ProxyableChangedSame proxy type for @Async, @Retryable and othersProxying
RestTestClient, context pausingNewWebTestClient without reactive code; paused test contextssection 4
HttpHeadersChangedNo longer extends MultiValueMapsection 5
javax.annotation, javax.inject, spring-jcl, UndertowRemovedUse the jakarta packages and Apache Commons Loggingsection 5
RestTemplate, JUnit 4 support, Jackson 2.xDeprecatedStill work in 7.0, removal is plannedsection 5

2. Java 17, Jakarta EE 11 and Kotlin 2.2 Baselines

Spring Framework 7 still needs only Java 17, like 6.x, so an app on Java 17 or 21 can upgrade without changing the JDK. Java 25 is the recommended version for production. The bigger change is on the Jakarta side. Spring Framework 7 requires Jakarta EE 11, so the servlet container (Tomcat or Jetty) and the JPA provider (Hibernate) must move to their EE 11 versions together with Spring.

Table comparing the library versions Spring Framework 6.2 and 7.0 need
Spring Framework 7 keeps Java 17 but needs the EE 11 version of every Jakarta library

For a web app, the upgrade means Tomcat 11.0 (or Jetty 12.1), Hibernate ORM 7.1+ and Hibernate Validator 9. Undertow has no release for Servlet 6.1 yet, so Spring 7 does not support it. Kotlin projects need Kotlin 2.2, and tests move to JUnit 6.

3. New Features in Spring Framework 7

All features in this part work without Spring Boot, and the Spring Boot 4 tutorial shows them with the starters. The examples use a small library app.

3.1. JSpecify Null-Safety for Spring APIs and Our Code

Every Spring 7 API says whether a value can be null. It uses JSpecify annotations, a shared standard for null annotations in Java, instead of Spring’s older @Nullable and @NonNullApi. Kotlin 2 reads them as nullable or non-null types.

In our own code, @NullMarked on a package makes every type non-null by default, and @Nullable marks the values that can be null.

@NullMarked                                         // everything in the package is non-null by default
package com.howtodoinjava.library.web;

public @Nullable Book find(String title) {          // null for an unknown title
  return books.get(title);
}

The annotations change nothing at runtime. A build-time checker such as NullAway (a compiler plugin) reports a call like find(title).copies() without a null check.

3.2. API Versioning in Spring MVC and WebFlux

Spring 7 adds API versioning to Spring MVC and WebFlux. A mapping declares its version with the new version attribute, such as @GetMapping(path = “/books/{title}”, version = “2”), and Spring calls the method that matches the version in the request. In WebMvcConfigurer, we read the version from a header, a query parameter, a path segment or a media type parameter.

@Override
public void configureApiVersioning(ApiVersionConfigurer configurer) {
  configurer.useQueryParam("version").setDefaultVersion("1");     // ?version=2, or 1 when missing
}

With methods for version 1 and version 2, the same URL returns different responses, and an unknown version gets a 400 response.

RequestHandlerResponse
GET /books/duneversion 1 (default)200 {“title”:”dune”,”copies”:3}
GET /books/dune?version=2version 2200 {“title”:”dune”,”copies”:3,”shelf”:”A3″}
GET /books/dune?version=3none400 Bad Request, InvalidApiVersionException

3.3. HTTP Service Groups With @ImportHttpServices

An HTTP interface client is an interface with @HttpExchange methods, and in 6.x each one needed its own @Bean method. In Spring 7, @ImportHttpServices lists the interfaces in groups, typically one group per remote server. Spring creates one HTTP client per group and a bean for every interface.

Say our library API reads stock counts from a warehouse service. We put the StockClient interface in the “stock” group and configure the RestClient of that group once.

@Configuration(proxyBeanMethods = false)
@ImportHttpServices(group = "stock", types = StockClient.class)
public class HttpClientsConfig {

  @Bean
  RestClientHttpServiceGroupConfigurer stockGroup(Environment env) {
    SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
    requestFactory.setReadTimeout(Duration.ofSeconds(2));
    return groups -> groups.filterByName("stock").forEachClient((group, builder) -> builder
        .baseUrl(env.getRequiredProperty("stock.base-url"))
        .requestFactory(requestFactory));
  }
}

We inject StockClient like any bean, and stockClient.copies(“dune”) returns 3 from the warehouse service.

3.4. @Retryable and @ConcurrencyLimit in the Core Framework

Spring 7 moves a trimmed version of Spring Retry into the framework. We turn on @Retryable and @ConcurrencyLimit from org.springframework.resilience.annotation with @EnableResilientMethods, as the intro example does.

By default, @Retryable retries any exception up to 3 times with a 1 second delay, and when all attempts fail, the caller gets the last exception. The name is the same as in Spring Retry, so only the imports change. The @ConcurrencyLimit annotation limits how many threads run a method at the same time, and the other callers wait for a free slot.

@Retryable(includes = ShelfBusyException.class, maxRetries = 2, delay = 100, multiplier = 2)
public int copies(String title) {
  return scanner.count(title);              // ShelfBusyException twice, then 3
}

@ConcurrencyLimit(2)
public String reserve(String title) {
  return scanner.reserve(title);            // 5 callers, at most 2 run at the same time
}

To retry in code, we use RetryTemplate from org.springframework.core.retry. Spring Framework 7 has no circuit breaker or rate limiter, so we keep Resilience4j for those.

3.5. Programmatic Bean Registration With BeanRegistrar

A @Bean method registers one bean. So in 6.x, one bean per configured value needed a low-level BeanDefinitionRegistryPostProcessor. The new BeanRegistrar interface has one method that gets a BeanRegistry and the Environment. Inside it, we register beans with plain Java loops and if checks.

For example, each library branch needs its own HelpDesk bean, so we register one bean per name in the library.branches property.

@Override
public void register(BeanRegistry registry, Environment env) {
  String[] branches = env.getProperty("library.branches", String[].class, new String[0]);
  for (String branch : branches) {                          // library.branches=north,south
    registry.registerBean(branch + "Desk", HelpDesk.class,  // northDesk, southDesk
        spec -> spec.supplier(ctx -> new HelpDesk(branch)));
  }
}

We activate a registrar with @Import on a @Configuration class, as the intro example does.

3.6. Optional Support in SpEL Expressions

In SpEL (the expression language of @Value), the operators ?. and ?: treat an empty Optional like null and use the value inside a present one. For example, #shelf ?: ‘none’ returns “none” for Optional.empty() and “a3” for Optional.of(“a3”). The expression #shelf?.toUpperCase() returns null or “A3”.

3.7. JmsClient for JMS Messaging

After RestClient and JdbcClient in 6.1, Spring 7 adds JmsClient, a fluent client to send and receive messages on a JMS queue or topic. It works on top of JmsTemplate, so our existing JMS connection setup keeps working.

3.8. Jackson 3 as the Default JSON Library

Spring 7 uses Jackson 3 (package tools.jackson) everywhere and falls back to the deprecated Jackson 2 support only when Jackson 3 is missing. The new HttpMessageConverters API sets up the converters (classes that turn objects into JSON and back) for Spring MVC and RestClient together, e.g. a JacksonJsonHttpMessageConverter with our own JsonMapper. The Jackson 3 migration guide covers the new imports and the changed defaults.

4. Testing Changes in Spring Framework 7

The main new test API is RestTestClient, a version of WebTestClient for apps without reactive code. We bind it to a controller, an application context, a MockMvc instance or a running server, and the test code stays the same.

RestTestClient client = RestTestClient.bindToApplicationContext(context).build();

client.get().uri("/books/dune?version=2").exchange()
    .expectStatus().isOk()
    .expectBody().jsonPath("$.shelf").isEqualTo("A3");

Existing tests see two changes. The Spring TestContext Framework supports JUnit 6 and deprecates its JUnit 4 support (SpringRunner). Also, a cached test context is paused while tests use another context, so its scheduled tasks do not run in between.

5. Removed and Deprecated APIs in Spring Framework 7

Removed APIs break the build or the startup, so we check them first. Deprecated APIs still work in 7.0.

API or featureStatus in 7.0Replacement
javax.annotation (@PostConstruct, @Resource) and javax.inject (@Inject)Removedjakarta.annotation and jakarta.inject
spring-jcl moduleRemovedApache Commons Logging 1.3
ListenableFutureRemovedCompletableFuture
HttpHeaders as a MultiValueMapChangedHttpHeaders methods
RestTemplateDeprecation announced, @Deprecated in 7.1, removal in 8.0RestClient
Jackson 2.x supportDeprecated, removal planned in 7.2Jackson 3.x

RestTemplate still works in 7.0, but new HTTP client code should use RestClient or HTTP interface clients. Existing RestTemplate code keeps working until 8.0, so we can migrate one client at a time.

6. Upgrading From Spring Framework 6.2 to 7

Spring Boot users get Spring Framework 7 with Spring Boot 4. Apps without Spring Boot change the spring-framework-bom version, which sets the version of every Spring module. We run the tests after each step, and apps on 5.3 first follow the javax to jakarta move from What’s New in Spring 6.

  1. Upgrade to the latest 6.2.x and fix every deprecation warning, because most 7.0 removals were deprecated in 6.x.
  2. Replace javax.annotation and javax.inject imports with their jakarta versions.
  3. Upgrade the server and Hibernate as section 2 lists, and move JSON code to Jackson 3.
  4. Replace ListenableFuture with CompletableFuture and remove any explicit spring-jcl dependency.

7. Conclusion

Spring Framework 7 keeps Java 17 but moves to Jakarta EE 11, JUnit 6 and Jackson 3. Most upgrade work comes from these versions and the removed javax annotations, not from our Spring code.

The new features are optional and work without Spring Boot. For new code, we use JSpecify annotations, @Retryable, RestClient and RestTestClient, because Spring 7.1 and 8.0 build on these APIs.

8. References

Happy Learning !!

Source Code on Github

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.