Spring Boot 3.0: What's Changing and How to Migrate Successfully
Back to Blog

Spring Boot 3.0: What's Changing and How to Migrate Successfully

7 min read
Read in Deutsch

The Next Major Release Is Here

On November 24, 2022, VMware released Spring Boot 3.0 as GA -- the first major release since Spring Boot 2.0 appeared in March 2018. Over four years lay between them, and the changes are correspondingly extensive. Spring Boot 3.0 is not simply an incremental update but a platform shift: new framework, new baseline, new namespace.

At encircle360, we have evaluated our first services and begun the migration over the past few days. Here is our overview of the most important changes -- and the practical steps we recommend for a successful migration.

Java 17 as the Minimum Requirement

Perhaps the most fundamental decision: Spring Boot 3.0 requires Java 17 as a minimum. No Java 11, no Java 8 -- anyone who has not yet migrated to Java 17 needs to do so now. As we described in our Java 17 article, Java 17 brings significant improvements with Records, Sealed Classes, and Pattern Matching. The fact that Spring now defines this version as the baseline sends a signal to the entire ecosystem: Java 17 is the new reference for enterprise Java.

For teams that have already taken this step, this is not an obstacle. For everyone else, we strongly recommend performing the Java 17 migration as a separate step before the Spring Boot upgrade -- not both at the same time.

From javax to jakarta: The Biggest Breaking Change

The switch from javax.* to jakarta.* is the single item that causes the most mechanical effort during migration. Spring Boot 3.0 is based on Jakarta EE 9+ and thus on the new namespace that the Eclipse Foundation introduced after taking over Java EE.

In concrete terms, this means: every import that previously started with javax. must be changed to jakarta..

// Before (Spring Boot 2.x)
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.validation.constraints.NotNull;
import javax.servlet.http.HttpServletRequest;

// After (Spring Boot 3.0)
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.validation.constraints.NotNull;
import jakarta.servlet.http.HttpServletRequest;

This affects not only your own code but also third-party libraries. Every library that uses javax.persistence, javax.validation, javax.servlet, or other Java EE APIs must be available in a Jakarta-compatible version. Hibernate 6.1, Jakarta Validation 3.0, Tomcat 10.1 -- Spring Boot 3.0 includes these versions, but you need to check your own dependencies.

In most IDEs, the namespace change can be accomplished with a global find-and-replace. IntelliJ even offers a dedicated migration function for this. The mechanical effort is high, but intellectually straightforward.

Spring Framework 6 Under the Hood

Spring Boot 3.0 is built on Spring Framework 6.0 -- the first major release of the core framework since 2017. Beyond the Jakarta EE transition, Spring Framework 6 brings a revised architecture that is more strongly oriented toward AOT (Ahead-of-Time) processing. This primarily impacts GraalVM support, which we will cover shortly.

For day-to-day development, little changes in the programming model. Controllers, services, repositories -- the familiar annotations and patterns work as before. The changes lie more in the internals that application developers rarely touch directly.

Security Configuration: Farewell to WebSecurityConfigurerAdapter

A change that affects many projects: the WebSecurityConfigurerAdapter has been removed. Anyone who previously defined their security configuration by extending this class must switch to the new component-based model using SecurityFilterChain beans.

// Before (Spring Boot 2.x) -- deprecated and removed in 3.0
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
            .antMatchers("/api/public/**").permitAll()
            .anyRequest().authenticated()
            .and()
            .oauth2ResourceServer().jwt();
    }
}

// After (Spring Boot 3.0) -- component-based
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/public/**").permitAll()
                .anyRequest().authenticated()
            )
            .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()));
        return http.build();
    }
}

Beyond the removal of the adapter, note that antMatchers() has been replaced by requestMatchers(), and the lambda-based DSL is now the preferred configuration style. The old method-chaining syntax with .and() is marked as deprecated.

GraalVM Native Image: First-Class Support at Last

In our GraalVM article, we wrote back in 2020 that Native Image was not a realistic option for Spring Boot applications. This changes fundamentally with Spring Boot 3.0. The integration of GraalVM Native Image is no longer an experimental extension but an officially supported feature.

Spring Boot 3.0 includes an AOT engine that analyzes the Application Context at build time and generates the necessary code to avoid reflection and dynamic proxies. This eliminates the tedious manual configuration files that were previously required.

For custom beans that require reflection at runtime, AOT hints can be registered:

@Configuration
@ImportRuntimeHints(MyRuntimeHints.class)
public class AppConfig {
}

public class MyRuntimeHints implements RuntimeHintsRegistrar {

    @Override
    public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
        hints.reflection()
            .registerType(MyDto.class, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
                MemberCategory.INVOKE_DECLARED_METHODS);
        hints.resources()
            .registerPattern("templates/*.html");
    }
}

The Maven and Gradle plugins support the native build directly. For Maven, a profile is all you need:

mvn -Pnative native:compile

Startup times drop to milliseconds, and memory consumption decreases dramatically. For microservices in Kubernetes environments and serverless scenarios, this is a real breakthrough -- and a direct answer to Quarkus and Micronaut, which previously held this advantage exclusively.

Observability with Micrometer

Spring Boot 3.0 introduces a new observability concept based on the Micrometer Observation API. Instead of instrumenting metrics and traces separately, there is now a unified abstraction: an Observation automatically generates both metrics and trace spans.

The Spring Framework itself is already comprehensively instrumented. HTTP requests, RestTemplate calls, JDBC queries -- all of these automatically generate observations that can be forwarded to Prometheus, Zipkin, Wavefront, or other backends. Anyone who previously wired up Micrometer timers and Brave spans manually can look forward to significantly less boilerplate.

HttpClient 5 and Other Breaking Changes

Apache HttpClient 4 is no longer supported. Anyone who included httpclient as a dependency must switch to httpclient5. This primarily affects projects that use Apache HttpClient directly or configure it via RestTemplate.

Another simplification concerns @ConfigurationProperties: the @ConstructorBinding annotation is no longer needed for classes with only one constructor. Spring Boot automatically detects that constructor binding is desired. For classes with multiple constructors, the annotation must still mark the intended constructor.

// Before (Spring Boot 2.x) -- @ConstructorBinding required
@ConfigurationProperties(prefix = "app")
@ConstructorBinding
public record AppProperties(String name, int port) {}

// After (Spring Boot 3.0) -- annotation unnecessary with a single constructor
@ConfigurationProperties(prefix = "app")
public record AppProperties(String name, int port) {}

Additionally, numerous APIs that were marked as deprecated in Spring Boot 2.x have been removed. Those who took the deprecation warnings in the 2.7.x line seriously have an advantage here.

Our Migration Roadmap

Based on our initial migration experiences, we recommend the following phased approach:

  1. First, update to Spring Boot 2.7.x -- the last 2.x version includes compatibility layers and deprecation hints that ease the transition.
  2. Ensure Java 17 -- if you haven't done so already, this is a prerequisite.
  3. Perform the javax-to-jakarta migration -- a global find-and-replace, followed by thorough testing.
  4. Convert the security configuration -- replace WebSecurityConfigurerAdapter with SecurityFilterChain beans.
  5. Check third-party libraries -- especially for Jakarta compatibility and HttpClient 5.
  6. Clean up deprecation warnings -- everything marked as deprecated in 2.7 will be missing in 3.0.
  7. Integrate Spring Boot 3.0 and run the tests -- any remaining issues will surface here.

We estimate two to four days per service, depending on complexity and the number of third-party dependencies. The javax-to-jakarta conversion is the most time-consuming single item because it cuts through all layers.

Our Conclusion

Spring Boot 3.0 is the most significant release since the 2.0 line. The migration is not a trivial upgrade, but the effort is well invested: Java 17 as the foundation, first-class GraalVM support, and a modern observability concept position Spring Boot for the years ahead.

The Native Image support in particular is a milestone. What was still considered experimental two years ago is now production-ready. This finally puts Spring Boot on par with Quarkus and Micronaut in terms of startup time and memory consumption -- with full compatibility with the existing ecosystem.

Spring Boot 2.7 will continue to receive security updates until November 2023. So the migration is not urgent, but planning should begin now. Anyone already running on Java 17 has already cleared the biggest hurdle.

Patrick Hütter

Written by

Patrick Hütter

Founder & Software Architect

Software architect, engineer and entrepreneur. Patrick has been building products and platforms for over a decade — from enterprise backends and cloud-native infrastructure to AI-powered applications. As founder of encircle360, he combines deep technical expertise with entrepreneurial vision, driving open source projects that create real impact.