Spring Boot 3 requires Java 17 and replaces javax.* with jakarta.*. Most migrations take 2-4 hours but hide surprises. Here's the complete migration guide with every breaking change documented.
JOptimize Team
If your team is still running Spring Boot 2.x, you're running on a framework that reached end-of-life in November 2023. Security patches are no longer backported. New Spring features are Spring Boot 3 only. And a number of the libraries you're probably using — Spring AI, GraalVM native support, Spring Modulith — require Spring Boot 3.
The migration isn't painless, but it's not a rewrite either. For most applications, the actual migration takes a few hours to a day. The surprising part is that the javax.* → jakarta.* rename, which looks like the biggest change, is often the easiest part. The Spring Security and Actuator changes catch more teams off guard.
Here's every breaking change you'll need to address:
1. Java 17 minimum — Spring Boot 3 requires Java 17 or later. If you're on Java 11, you need to upgrade the JDK first.
2. javax. → jakarta. namespace** — Every class from the old Java EE / Jakarta EE namespace has moved. javax.servlet.* becomes jakarta.servlet.*, javax.persistence.* becomes jakarta.persistence.*, and so on.
3. Spring Security 6 — Major breaking changes to the security configuration API. The old WebSecurityConfigurerAdapter is gone entirely. Lambda-based DSL is now required.
4. Spring Data 3 — CrudRepository.findById() now returns Optional (it did before, but behavior changed in edge cases). Auditing annotations moved packages.
5. Hibernate 6 — New naming strategy, dialect changes, and behavior differences for legacy mappings.
6. Actuator changes — Several endpoint paths changed, and the health endpoint response structure was updated.
7. Spring MVC changes — MockMvc import paths changed, some deprecated methods removed.
Before touching Spring, upgrade to Java 17 (or 21, which is the current LTS and worth targeting directly):
<!-- pom.xml --> <properties> <java.version>21</java.version> <maven.compiler.source>21</maven.compiler.source> <maven.compiler.target>21</maven.compiler.target> </properties>
Run your test suite after the Java upgrade and fix anything that broke before touching Spring. Java 17 removed some APIs that were deprecated in Java 11. The most common issue is code using SecurityManager, sun.* internal APIs, or modules that need --add-opens flags.
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.3.0</version> <!-- Or latest 3.x --> </parent>
Spring Boot's dependency management will automatically update Hibernate, Spring Security, Spring Data, and most other managed dependencies to compatible versions. You don't need to update them individually.
This is the most mechanical part of the migration. Every import that starts with javax. from the Jakarta EE namespace needs to be updated:
javax.servlet.* → jakarta.servlet.* javax.persistence.* → jakarta.persistence.* javax.validation.* → jakarta.validation.* javax.transaction.* → jakarta.transaction.* javax.annotation.* → jakarta.annotation.* javax.xml.bind.* → jakarta.xml.bind.* javax.ws.rs.* → jakarta.ws.rs.*
Note: javax.sql.* does NOT change — it's part of the Java SE API, not Jakarta EE.
The fastest way to do this migration is with the OpenRewrite migration recipe:
<!-- Add to pom.xml --> <plugin> <groupId>org.openrewrite.maven</groupId> <artifactId>rewrite-maven-plugin</artifactId> <version>5.38.0</version> <configuration> <activeRecipes> <recipe>org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_3</recipe> </activeRecipes> </configuration> <dependencies> <dependency> <groupId>org.openrewrite.recipe</groupId> <artifactId>rewrite-spring</artifactId> <version>5.19.0</version> </dependency> </dependencies> </plugin>
mvn rewrite:run # OpenRewrite automatically: # - Updates javax.* imports to jakarta.* # - Updates Spring Security config to lambda DSL # - Updates deprecated API usages # - Bumps managed dependency versions
OpenRewrite handles the vast majority of mechanical changes automatically. After running it, review the diff and run your test suite.
This is the change that breaks the most teams. Spring Security 6 removed WebSecurityConfigurerAdapter. If you extended it, your entire security config needs to be rewritten:
// Spring Boot 2 — NO LONGER WORKS @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/api/public/**").permitAll() .antMatchers("/api/admin/**").hasRole("ADMIN") .anyRequest().authenticated() .and() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .csrf().disable(); } } // Spring Boot 3 — lambda DSL, SecurityFilterChain bean @Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth .requestMatchers("/api/public/**").permitAll() .requestMatchers("/api/admin/**").hasRole("ADMIN") .anyRequest().authenticated() ) .sessionManagement(session -> session .sessionCreationPolicy(SessionCreationPolicy.STATELESS) ) .csrf(csrf -> csrf.disable()); return http.build(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }
The logic is the same — the syntax changed to avoid method chaining ambiguity and to make the API more composable. OpenRewrite handles this transformation automatically for most common patterns.
Hibernate 6 has stricter behavior around implicit join aliases and some query syntax changes:
// Hibernate 5 — worked implicitly @Query("SELECT o FROM Order o WHERE o.customer.email = :email") List<Order> findByCustomerEmail(@Param("email") String email); // Hibernate 6 — still works, but cross joins are now more strictly handled // If you see: QueryException: 'Implicit cartesian join' // Fix with explicit JOIN: @Query("SELECT o FROM Order o JOIN o.customer c WHERE c.email = :email") List<Order> findByCustomerEmail(@Param("email") String email);
Also, Hibernate 6's new naming strategy may affect your column name resolution if you were relying on implicit naming:
# application.yml — if your column names break after migration spring: jpa: hibernate: naming: physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl # Or the Spring Boot implicit strategy for backwards compatibility: # physical-strategy: org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy
# Spring Boot 2 — default paths /actuator/health /actuator/metrics # Spring Boot 3 — same paths but response format changed # health now uses groups more prominently # Liveness and readiness are separate components: management: endpoint: health: probes: enabled: true # Enables /actuator/health/liveness and /actuator/health/readiness
If your Kubernetes liveness/readiness probes pointed directly to /actuator/health, they still work. But you should update them to use the dedicated probe endpoints for cleaner semantics.
Run these checks after completing the mechanical migration:
javax.* and jakarta.* in the same class — compile error; run the full OpenRewrite recipe to catch all occurrences@Query methods; compare query logs before and afterMigrating to Spring Boot 3 requires Java 17+, the javax.* → jakarta.* namespace change, Spring Security lambda DSL, and Hibernate 6 query compatibility fixes. OpenRewrite automates most of the mechanical work. Budget a day for a medium-sized application, run your full test suite after each step, and pay extra attention to security configuration and Hibernate query behavior.
Spring Boot 3 migrations sometimes introduce subtle performance changes — Hibernate 6's different query generation can create new N+1 patterns. JOptimize helps you catch these post-migration.
Migrate confidently — then analyze for regressions.
Master Spring Boot, security, and Java performance with hands-on courses.
JOptimize finds N+1 queries, EAGER collections, and 70+ other issues in your Java codebase — in under 30 seconds.