一句話先說結論: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 年末發佈,踩在三大變化之上:
- JDK 強制升到 17+,Java 8 / 11 不再支持。
- Jakarta EE 9+ 命名空間遷移:
javax.*全部改為jakarta.*。 - 生態大版本同步升級: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 更不容易漏。
