Have you ever tried to upgrade a Spring Boot 2.x project to 3.x, only to be overwhelmed by a flood of compilation errors?
OSS support for Spring Boot 2.7.x ended at the end of 2023, and security patches have stopped. Migration is unavoidable, but 3.x contains many breaking changes, and without knowing the procedure, your work can grind to a halt quickly.
This article focuses on three major changes — javax→jakarta replacement, migration to SecurityFilterChain, and removal of spring.factories — and walks through how to handle them. Use the checklist at the end as your working sheet.
What Changed in Spring Boot 3.x
The main breaking changes are these three:
- javax→jakarta package name change (transition from Java EE to Jakarta EE 9)
- WebSecurityConfigurerAdapter removal (forced migration to SecurityFilterChain)
- spring.factories removal (changes to AutoConfiguration registration)
In addition, Spring Boot 3.x requires Java 17 or higher. You cannot even start the application on Java 8 or 11.
Whether to migrate all at once or in stages depends on your test coverage and project size. For large projects with thin test coverage, it is safer to progress the migration on a feature branch and merge into main incrementally.
Preparation Before Migration
What Is the javax to jakarta Migration? (Terminology)
The “javax to jakarta migration” refers to the work of bulk-replacing import statements in source code from the javax.* prefix to jakarta.*, which became necessary when Java EE transitioned to Jakarta EE 9. Since Spring Boot 3.x is built on Jakarta EE 9, this work is unavoidable when upgrading from 2.x.
There are three key points:
- Only packages included in the Jakarta EE specification are affected (Servlet / Persistence / Validation / Annotation, etc.)
- JDK standard
javax.*(javax.sql/javax.crypto/javax.net) do NOT need replacement - Not just your application — third-party libraries also need to be upgraded to Jakarta-compatible versions
Concrete replacement procedures and commands are explained in the next section.
First, change the Spring Boot version and Java version in build.gradle.
plugins {
id 'org.springframework.boot' version '3.2.0'
id 'io.spring.dependency-management' version '1.1.4'
id 'java'
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
After making the change, run ./gradlew dependencies to check whether any incompatible third-party libraries exist. Older versions of mapstruct, querydsl, and springfox often lack Jakarta EE support, so be careful.
javax → jakarta Package Name Change
Since Spring Boot 3.x is built on Jakarta EE 9, you need to replace javax.* imports with jakarta.*. The main targets are:
javax.servlet.*→jakarta.servlet.*javax.persistence.*→jakarta.persistence.*javax.validation.*→jakarta.validation.*javax.annotation.*→jakarta.annotation.*
As a caveat, javax.sql.*, javax.crypto.*, and javax.net.* are not part of Jakarta EE and do not need to be replaced. Replacing them by mistake will cause compilation errors.
Bulk Replacement via Command Line
find src -name "*.java" | xargs sed -i \
-e 's/javax\.servlet/jakarta.servlet/g' \
-e 's/javax\.persistence/jakarta.persistence/g' \
-e 's/javax\.validation/jakarta.validation/g' \
-e 's/javax\.annotation/jakarta.annotation/g'
If you use IntelliJ IDEA, it is easier to go to “Edit > Find > Replace in Files” and use the regex javax\.(servlet|persistence|validation|annotation) replaced with jakarta.$1. After replacing, always run a build to check for missed entries.
Migration to SecurityFilterChain
In Spring Security 6.x, WebSecurityConfigurerAdapter has been completely removed. If you build 2.x code as is, you will get compilation errors.
Before (2.x)
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/api/public/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin();
}
}
After (3.x)
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**").permitAll()
.anyRequest().authenticated()
)
.formLogin(Customizer.withDefaults());
return http.build();
}
}
The changes can be summarized as follows:
- Remove
extends WebSecurityConfigurerAdapterand change to a@Beanmethod authorizeRequests()→authorizeHttpRequests()antMatchers()→requestMatchers()- The lambda DSL becomes the standard, and
.and()chaining is no longer needed
UserDetailsService and PasswordEncoder can still be defined with @Bean as before. For Basic authentication patterns, see Implementing Spring Boot Security Basic Authentication, and for JWT authentication, see Implementing JWT Authentication.
spring.factories Removal
If you created your own AutoConfiguration or starter, the registration method has changed.
Before (spring.factories)
# META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.MyAutoConfiguration
After (.imports file)
# META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
com.example.MyAutoConfiguration
The new file path is META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports, and you simply list class names one per line. For details on how AutoConfiguration works, see How Spring Boot AutoConfiguration Works.
Checkpoints for Java 17 Compatibility
Migration Points for Related Libraries
In addition to the three main breaking changes, Spring Boot 3.x also affects compatibility with surrounding libraries. Here are the points where real projects tend to get stuck.
Migrating to Hibernate 6
Spring Boot 3.x is updated to the Hibernate 6 series. SQL generation and type inference have changed, and the way to specify the @Type annotation as well as the auto-detection behavior of hibernate.dialect differ from before. If you use custom UserTypes, you will be affected by API changes, so be sure to also read the Hibernate 6 release notes. Notes on implementing soft deletes with @SQLDelete / @SQLRestriction are summarized in How to Implement Soft Delete with Spring Boot + JPA.
Spring Cloud Sleuth Removal → Micrometer Tracing
The distributed tracing library has been completely replaced from Spring Cloud Sleuth to Micrometer Tracing. Since the spring-cloud-starter-sleuth dependency no longer works as-is, you need to swap it out for bridges such as micrometer-tracing-bridge-brave. For concrete configuration steps, see Introducing Distributed Tracing with Micrometer Tracing and Zipkin on Spring Boot 3.2+.
Behavioral Differences in Jackson 2.14
Jackson has also been updated to the 2.14 series, and the locale interpretation of @JsonFormat and the default serialization of BigDecimal have changed slightly. There may be differences in response JSON, so if you have contract tests or snapshot tests, regenerate them immediately after migration.
Should You Consider Native Image?
Starting with Spring Boot 3.0, GraalVM Native Image is officially supported, enabling significant reductions in cold start time. If you are considering moving to Native Image alongside the 3.x migration, refer to How to Native-Compile with GraalVM Native Image on Spring Boot 3.x. Since additional work such as preparing reflection hints is required, it is safer to first complete the 3.x migration on the JVM and then proceed gradually.
Java 17 has strengthened encapsulation through the module system, so libraries that accessed internal classes via reflection may no longer work. With dependency versions that are Spring Boot 3.x compatible, --add-opens is mostly unnecessary, so if old JVM options remain, remove them and verify operation. Code that directly uses sun.* packages will result in compilation errors, so it needs to be replaced with standard APIs.
Verification After Migration
Once the migration is complete, verify in the following order.
- Run
./gradlew buildto confirm compilation and tests pass - Start the application and check that
actuator/healthreturnsUP - Perform smoke tests on the main endpoints
- Use MockMvc tests to review Security configuration
@SpringBootTest and @WebMvcTest will mostly work as-is, but be sure to check the authentication settings in tests to match the Security configuration changes.
Migration Checklist
Pre-Migration
- Confirm migration to Java 17 or higher
- Change Spring Boot version in
build.gradleto 3.x - Verify dependency compatibility with
./gradlew dependencies
javax→jakarta Replacement
- Replace
javax.servlet,javax.persistence,javax.validation,javax.annotation - Check that
javax.sqlandjavax.cryptoare NOT replaced (replacement not needed) - Confirm the build succeeds
Spring Security Migration
- Change all
WebSecurityConfigurerAdapterinheritance toSecurityFilterChain @Bean - Change
authorizeRequests()→authorizeHttpRequests()andantMatchers()→requestMatchers()
spring.factories Removal
- Migrate the
EnableAutoConfigurationentries inMETA-INF/spring.factoriesto the.importsfile
Verification
- Build succeeds and tests pass
- App starts and
actuator/healthreturnsUP
Summary
Migration to Spring Boot 3.x can look like a lot, but if you focus on the three points — javax→jakarta, SecurityFilterChain, and spring.factories — most of the errors can be resolved. Cut a migration branch and knock out one item at a time, and it will finish faster than you expect.
For managing configurations per environment, also see Leveraging Spring Boot Profiles.