Back to Blog
spring-boot-3jakarta-eemigrationjavaspringupgrade

Migrating to Spring Boot 3: The Jakarta EE Namespace Change and What Else Broke (2026)

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.

J

JOptimize Team

May 29, 2026· 9 min read

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.


What Changed: The Complete List

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 3CrudRepository.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 changesMockMvc import paths changed, some deprecated methods removed.


Step 1: Upgrade Java First

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.


Step 2: Update Spring Boot Version

<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.


Step 3: The javax → jakarta Rename

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.


Step 4: Fix Spring Security Configuration

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.


Step 5: Hibernate 6 Changes

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

Step 6: Actuator Path Changes

# 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.


What to Check After Migration

Run these checks after completing the mechanical migration:

  1. All tests pass — especially integration tests that boot the Spring context
  2. Security configuration — test that protected endpoints still require authentication and that public endpoints are accessible
  3. Database schema — run a migration dry-run if Hibernate naming strategy changed
  4. Actuator endpoints — verify health, metrics, and Prometheus endpoints respond correctly
  5. Jackson serialization — check that existing JSON API responses haven't changed shape

Common Mistakes to Avoid

  • Upgrading Spring Boot without upgrading Java first — Spring Boot 3 refuses to start on Java 11; sort out Java first
  • Mixing javax.* and jakarta.* in the same class — compile error; run the full OpenRewrite recipe to catch all occurrences
  • Not testing security after migration — the Security 6 lambda DSL has slightly different precedence rules; test all access control scenarios explicitly
  • Ignoring Hibernate implicit join warnings — Hibernate 6 may silently generate different SQL for some @Query methods; compare query logs before and after

Summary

Migrating 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.


Analyze Your Migrated App for Performance Issues

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.

Want to go deeper?

Master Spring Boot, security, and Java performance with hands-on courses.

Detect issues in your project

JOptimize finds N+1 queries, EAGER collections, and 70+ other issues in your Java codebase — in under 30 seconds.