Spring Boot 3 升级不是简单的版本号 bump,而是一次从 Java 8/11 到 Java 17、从 javax 到 jakarta 的生态跃迁。本文基于生产项目从 2.7 迁到 3.2 的真实踩坑,总结 7 个最致命的雷区与可落地的解决清单,帮你把升级周期从”两周噩梦”压缩到两天。更重要的是,Spring Boot 2.7 已于 2023 年结束 OSS 支持,继续停在 2.x 意味着拿不到安全补丁——升级不再是”想不想”,而是”必须做”。
升级前置:先确认你的基线
动手之前先对齐三条硬指标,任何一条不满足都会在中途暴雷:
- JDK 17 是地板:Spring Boot 3.0 起彻底放弃 JDK 8/11,最低运行环境为 Java 17。
- Spring Framework 6:底层容器升级到 6.x,要求 Jakarta EE 9+。
- 内建依赖大版本齐跳:Hibernate 6、Tomcat 10、Jetty 12,间接依赖需重新对齐。
除了技术基线,还要评估业务侧的”隐形成本”:团队是否熟悉 Java 17 新语法(record、密封类、模式匹配)、是否有依赖 JDK 8 私有 API 的老代码、CI 镜像是否预装了 JDK 17。先把 Maven 编译目标锁到 17,避免本地用 JDK 8 跑通、CI 上却崩:
<properties>
<java.version>17</java.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
雷区一:javax → jakarta 命名空间(最痛)
这是所有坑里杀伤面最广的一个。Spring Boot 3 全面切换 Jakarta EE 9,包名从 javax.* 改为 jakarta.*。任何残留的 javax 导入都会在运行时报 NoClassDefFoundError 或 ClassNotFoundException,而且编译期完全看不出来——因为编译只检查语法,不检查运行时类加载。隐蔽的还有 javax.annotation(如 @PostConstruct)和 javax.transaction,它们同样整包搬家。
// 升级前(Spring Boot 2.x)
import javax.persistence.Entity;
import javax.validation.Valid;
import javax.servlet.http.HttpServletRequest;
import javax.annotation.PostConstruct;
// 升级后(Spring Boot 3.x)
import jakarta.persistence.Entity;
import jakarta.validation.Valid;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.annotation.PostConstruct;
全量替换最稳的方式是 IDE 全局重构(IntelliJ 的 Migrate to Jakarta EE 动作),而不是手动一个个改。特别注意第三方依赖:只要它还在用 javax,就必须升级到对应的 Jakarta 兼容版本,否则类加载直接失败。Lombok、MapStruct 这类注解处理器也要确认版本支持 Jakarta,否则实体上的 @Data、@Mapper 会悄悄失效。
雷区二:Spring Security 6 配置范式彻底重写
WebSecurityConfigurerAdapter 在 3.x 中被标记为废弃并移除,旧的那套 extends + @Override configure() 写法直接编译不过。新范式是声明一个 SecurityFilterChain Bean,把所有规则用 lambda DSL 串起来:
// 旧写法(已废弃)
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated().and().formLogin();
}
}
// 新写法:SecurityFilterChain Bean
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
.formLogin(Customizer.withDefaults());
return http.build();
}
}
除了 authorizeRequests() → authorizeHttpRequests(),方法安全注解 @PreAuthorize 的 SpEL 表达式解析器也换了实现;CSRF 默认对 GET 之外的方法生效,若前端是前后端分离的无状态 JWT 方案,记得显式 csrf(csrf -> csrf.disable()) 并改用无状态会话策略。安全配置复杂时,建议单独排期,别和包名迁移混在一起改。
雷区三:Springfox 退役,SpringDoc OpenAPI 上岗
Swagger 的 springfox 3.x 不兼容 Jakarta 与 Spring 6,强留只会得到一堆空指针。直接换成 springdoc-openapi。注意它的 groupId 也从 io.springfox 换成了 org.springdoc:
<!-- 移除 springfox -->
<!-- <dependency> io.springfox:springfox-boot-starter </dependency> -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.3.0</version>
</dependency>
注解也要跟着换:@Api → @Tag、@ApiOperation → @Operation、@ApiModel → @Schema。访问地址从 /swagger-ui.html 变成 /swagger-ui/index.html,前端联调的同学记得同步改书签,否则会收获一个 404 页面。
雷区四:Hibernate 6 与命名策略默认变更
Hibernate 6 带来了新的物理命名策略默认值 CamelCaseToUnderscoresNamingStrategy,这点和 2.x 行为不同。如果你在旧版里依赖了别的命名策略生成的字段名,升级后可能出现”实体对得上、SQL 字段对不上”的诡异问题。此外,MySQL 方言从 6.x 起改为自动探测,不再需要手写 org.hibernate.dialect.MySQL5Dialect。显式声明策略最稳妥:
# 显式锁定物理命名策略,避免字段名意外漂移
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy
实体映射层面还有两个高频小坑:@GeneratedValue(strategy = GenerationType.IDENTITY) 在 MySQL 下行为不变,但如果你用 @Enumerated(STRING) 持久化枚举,6.x 对大小写更敏感;自增主键配合批量插入时要留意 JDBC 批处理开关 rewriteBatchedStatements=true 是否仍生效。建议在迁移后跑一轮真实 DDL 自动校验,而不是只信编译通过。
雷区五:路径匹配 PathPatternParser 成为默认
Spring MVC 默认从 AntPathMatcher 切到 PathPatternParser。后者更快、内存占用更低,但不再支持 ** 双通配,也不支持路径中的正则表达式。老代码里写 /static/** 或 /api/{version:v1|v2}/** 这类路由规则会直接解析失败。临时回退开关如下(仅作过渡,长期应改写路由):
# 临时回退到 Ant 风格(不推荐长期使用)
spring.mvc.pathmatch.matching-strategy=ant_path_matcher
雷区六:配置属性重命名速查
一批配置项在 3.x 中被重新归纳,最容易被忽略的是 Redis 相关属性整体挪到了 spring.data.redis 下。下表是常见的迁移对照:
| 旧配置(2.x) | 新配置(3.x) |
| spring.redis.host | spring.data.redis.host |
| spring.redis.port | spring.data.redis.port |
| spring.redis.database | spring.data.redis.database |
| server.max-http-header-size | server.max-http-request-header-size |
| spring.jpa.open-in-view | spring.jpa.open-in-view(默认 false) |
如果你之前按 Redis 缓存设计:穿透、击穿、雪崩与最佳实践 搭过缓存层,升级后第一件事就是改这一组前缀,否则应用启动读不到连接配置,会默默连本地 6379 或干脆报错。另一个易漏点是 spring.jpa.open-in-view 默认值变了,可能让长事务悄悄拉长数据库连接占用。
雷区七:循环依赖检测更严 + 第三方适配
Spring Boot 2.6 起默认禁止 Bean 循环依赖,3.x 延续并强化。常见组件需要同步升级到 Jakarta 兼容版本,版本矩阵如下:
| 组件 | 2.x 版本 | 3.x 兼容版本 |
| MyBatis Spring | 2.2.x | 3.0.3+ |
| PageHelper | 5.3.x | 6.1.0+ |
| Druid | 1.2.x | 1.2.20+ |
| Redisson | 3.23.x | 3.27.0+ |
遇到实在解不开的循环依赖,可用应急开关顶一顶(但务必记到技术债里尽快重构,因为它会掩盖设计坏味道):
# 应急(不建议长期开启)
spring.main.allow-circular-references=true
升级 Checklist 与回滚预案
- 第一步:本地把 JDK 切到 17,
mvn clean compile通过。 - 第二步:全局 migrate to Jakarta,逐个模块编译。
- 第三步:重写 Security 配置、替换 Springfox→SpringDoc。
- 第四步:批量替换
spring.redis.*→spring.data.redis.*。 - 第五步:对齐第三方库版本矩阵(见上表)。
- 第六步:把升级后的回归测试交给 GitHub Actions 实战:从零搭建 CI/CD 流水线,每次提交自动跑单测与集成测试,避免”本地好用线上炸”。
- 回滚预案:保留 2.7 分支 tag,数据库层若涉及字段名变更(Hibernate 命名策略),先按 MySQL 索引底层原理与最左前缀 的思路核对表结构差异,必要时用灰度发布逐步切流。
总结
Spring Boot 3 升级的 7 个雷区里,jakarta 命名空间和Spring Security 6 重写是工作量主体,配置重命名最容易被漏。核心心法是:分包、分模块、先编译后运行,并用 CI 守住回归底线。按本文清单走,生产项目两天内完成平滑迁移并非难事;更重要的是,迈过这道坎后你就能享受 Java 17 虚拟线程、GraalVM 原生镜像等新一代特性的红利。




