StackTraces
Spring Boot已驗證

Spring Boot 2 升 3:javax 變 jakarta 遷移

@debug_master32 天前更新閱讀 6 min00

一句話先說結論:Spring Boot 3 是基於 Spring Framework 6 的平臺級升級,最低要求 Java 17,並把整個生態從 javax.* 遷移到 jakarta.*。升級後最先崩的往往是滿屏的包找不到——javax.persistence、javax.servlet 這些全都要改成 jakarta.*。再疊上 Spring Security 6 移除了 WebSecurityConfigurerAdapter、Hibernate 升到 6,升級是一項“重命名 + 重寫”的工程。

背景

Spring Boot 3.x 於 2022 年末發佈,踩在三大變化之上:

  1. JDK 強制升到 17+,Java 8 / 11 不再支持。
  2. Jakarta EE 9+ 命名空間遷移:javax.* 全部改為 jakarta.*。
  3. 生態大版本同步升級:Spring Framework 6、Spring Security 6、Hibernate 6、Tomcat 10。

所以它不是“小版本點一下”,而是把好幾條兼容性邊界同時跨過去。

現象

升級之後最常見的三類報錯:

一是 Java 版本不夠:

UnsupportedClassVersionError: com/example/Application (class file version 61.0)
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:756)
    at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:169)
    at java.net.URLClassLoader.defineClass(URLClassLoader.java:467)
    at java.net.URLClassLoader.access$100(URLClassLoader.java:73)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:368)
    at java.net.URLClassLoader$3.run(URLClassLoader.java:372)

(class file version 61.0 對應的就是 Java 17。)

二是滿屏的包找不到:

import javax.persistence.Entity;      // error: package javax.persistence does not exist
import javax.servlet.http.HttpServletRequest;  // error
import javax.validation.constraints.NotNull;   // error

Hibernate/JPA 實體的 @Entity、@Column 等註解直接失效。

三是運行時 ClassNotFoundException——編譯過了但依賴版本沒對上:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfiguration.class]: Invocation of init method failed
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1770)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:600)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:523)
    at org.springframework.beans.factory.support.AbstractBeanDefinition.lambda$resolveBeforeInstantiation$4(AbstractBeanDefinition.java:2087)
    ...
Caused by: java.lang.ClassNotFoundException: javax.persistence.EntityManagerFactory
    at java.net.URLClassLoader.findClass(URLClassLoader.java:387)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:416)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:348)
    ... 68 more

或者使用已被移除的 WebSecurityConfigurerAdapter 時:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'springSecurityFilterChain' defined in class path resource [com/example/config/SecurityConfig.class]: Failed to instantiate [org.springframework.security.web.SecurityFilterChain]: Factory method 'springSecurityFilterChain' threw exception
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:181)
    at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:654)
    ...
Caused by: java.lang.NoSuchMethodError: 'void org.springframework.security.config.annotation.web.configuration.HttpSecurityConfigurerAdapter.init(boolean)'
    at com.example.config.SecurityConfig.configure(SecurityConfig.java:15)
    at org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration.getHttpSecurity(WebSecurityConfiguration.java:128)
    ... 72 more

根因分析

這個 javax → jakarta 的變化,源頭不在 Spring,而在整個 Java 企業生態。

2017 年 Oracle 把 Java EE 交給 Eclipse 基金會,規範、參考實現、TCK 都捐了,但 javax 這個命名空間商標 Oracle 沒放。Eclipse 基金會不能修改 javax.* 包並以 Java EE 名義發佈,於是整體改名:Java EE 變成 Jakarta EE,企業級包名從 javax.* 換成 jakarta.*。Jakarta EE 9 是第一次純改名發佈,不做新特性,就只是換包名。Spring Boot 3 / Spring Framework 6 跟隨採用了這套新命名空間。

這裡有個最容易誤傷的點:不是所有 javax 都要改。只有原來屬於 Java EE 的包才變了,JDK 核心自帶的 javax 沒動:

要改(原 Java EE) 不改(JDK 自帶)
javax.persistence → jakarta.persistence javax.sql
javax.servlet → jakarta.servlet javax.crypto
javax.validation → jakarta.validation javax.net.ssl
javax.annotation → jakarta.annotation javax.swing
javax.inject / javax.transaction → jakarta.* javax.naming

所以批量替換時得用白名單,不能無腦把所有 javax 都換掉。

解決方案

1. 先升 JDK 到 17

pom.xml:

<properties>
    <java.version>17</java.version>
</properties>

Gradle:

java {
    sourceCompatibility = JavaVersion.VERSION_17
    targetCompatibility = JavaVersion.VERSION_17
}

2. 批量替換 Jakarta 包名

只改 Jakarta EE 相關的那幾個前綴,用 IDE 的 Find in Files → Replace in Files 一鍵替換:

javax.servlet    → jakarta.servlet
javax.persistence → jakarta.persistence
javax.validation → jakarta.validation
javax.annotation → jakarta.annotation
javax.transaction → jakarta.transaction

替換後:

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

// After (Boot 3.x)
import jakarta.persistence.Entity;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.constraints.NotNull;

依賴座標也跟著換,例如:

// Boot 2.x
implementation 'javax.validation:validation-api'
// Boot 3.x
implementation 'jakarta.validation:jakarta.validation-api'

3. 重寫 Spring Security 設定

Spring Security 6 徹底移除了 WebSecurityConfigurerAdapter,改成 SecurityFilterChain Bean + Lambda DSL:

// ❌ Boot 2.x(已删除)
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/public/**").permitAll()
            .anyRequest().authenticated();
    }
}

// ✅ Boot 3.x
@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests(auth -> auth
                .requestMatchers("/public/**").permitAll()
                .anyRequest().authenticated()
            );
        return http.build();
    }
}

方法是 antMatchers → requestMatchers、authorizeRequests → authorizeHttpRequests,鏈式 .and() 改成 Lambda。

4. 檢查第三方依賴和設定項

  • Swagger/SpringFox 不支持 Jakarta,換成 springdoc-openapi-starter-webmvc-ui。
  • 老版 Hibernate(5.x)不支持 Jakarta,必須升到 Hibernate 6。
  • 設定鍵有一批被重命名/刪除,典型如 spring.redis.* → spring.data.redis.*。可以臨時引入 spring-boot-properties-migrator,它會在啟動時打印哪些舊設定鍵該改。
  • Hibernate 6 的 HQL / dialect 有差異,session.createQuery("from User") 最好改成帶上返回類型 createQuery("from User", User.class)。

5. 用工具省力

大項目手動替換容易漏。可以用 OpenRewrite 的遷移 recipe 自動重寫 javax.* imports 和依賴版本;spring-boot-properties-migrator 負責提示設定項遷移。

小結

  • 這次升級的關鍵詞是“重命名 + 重寫”:javax → jakarta、WebSecurityConfigurerAdapter → SecurityFilterChain、Hibernate 5 → 6。
  • javax 替換一定要分清楚哪些是 Jakarta EE、哪些是 JDK 自帶,別誤傷 javax.sql 這類。
  • 順序建議:先升 JDK 17 → 再做包名替換 → 重寫 Security → 逐個校驗第三方庫的 Jakarta 兼容版本。
  • 升級後跑一遍 spring-boot-properties-migrator,比手工翻 migration guide 更不容易漏。

References(參考來源)

最後更新於 2026-09-09

這篇幫到你了嗎?

剛解決了一個棘手的報錯?花兩分鐘記錄下來,幫助下一個遇到同樣問題的開發者。

貢獻一條解法