Spring Boot 3 升级踩坑实录:从 2.x 平滑迁移到 3.x

Spring Boot 3 升级是近年 Java 后端最值得做、也最容易踩坑的一次大版本跨越。它把基线 JDK 拉到 17,底层换上 Spring Framework 6,最关键的是整个生态从 javax.* 迁移到 jakarta.* 命名空间。本文基于真实迁移经验,把最痛的几个坑、对应的改法,以及稳妥的分阶段回滚策略一次性讲清楚,帮你把升级从“玄学”变成一份可执行的清单。

一、为什么必须升:JDK 17 基线与 Spring Framework 6

Spring Boot 3 硬性要求 Java 17 及以上,Spring Framework 6 也只支持 Jakarta EE 9+。这意味着两件事:一是编译与运行环境必须先到 JDK 17;二是大量底层 API 与第三方库要同步翻新。旧项目若还停在 JDK 8/11,第一步不是改 Spring,而是先把 JDK 升上去。

建议先在独立分支完成 JDK 升级与编译验证,再叠加 Spring Boot 3 的改动。这样能清晰区分“是 JDK 版本带来的问题”还是“Spring 带来的问题”,排错时少绕弯路。容器化部署可参考我们的 Docker Compose 一键编排后端服务 把多环境镜像统一管理。

除了版本号,Java 17 还带来了 Records、Sealed Classes、Pattern Matching for instanceof 等语言特性,升级后业务代码可以更简洁。构建侧也要跟上:Maven 需 3.6.3+、Gradle 需 7.5+,相关编译插件链最好一并升级,避免“工具链不支持 17”这类低级阻塞。

另一个容易被忽视的点:Spring Boot 2.6 起已默认禁止 Bean 循环依赖,但很多团队还停留在 2.4/2.5 且开着白名单。升级到 3.x 后循环依赖会直接启动失败,建议先用 spring.main.allow-circular-references=true 临时放行,再逐个拆掉依赖闭环,别等到上线夜才爆雷。

二、最大断点:Jakarta EE 命名空间 javax → jakarta

这是升级失败的头号原因。Hibernate、Validation、Servlet、Persistence、Annotation 等全部从 javax 改为 jakarta。任何残留 javax.persistence、javax.validation 的代码都会在运行时抛出 ClassNotFoundException 或直接导致启动失败。

2.1 依赖坐标变化

<!-- Spring Boot 2.x:常单独声明 javax 依赖 -->
<dependency>
  <groupId>javax.persistence</groupId>
  <artifactId>javax.persistence-api</artifactId>
</dependency>

<!-- Spring Boot 3.x:Jakarta 已随 starter 自带,通常无需单独声明 -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

除了显式声明的依赖,真正麻烦的是传递依赖。老版本的 Hibernate Validator、Tomcat、Jackson 可能仍拉入 javax 包,需要把这些库整体升到 Jakarta 版本。官方提供了 spring-boot-properties-migrator 与 OpenRewrite 的 SpringBoot2to3 recipe,可自动完成大部分包名与配置键的改写,强烈建议先跑一遍再人工校对,能省下大把体力活。

2.2 代码中的包名替换

// 升级前
import javax.persistence.Entity;
import javax.validation.Valid;
import javax.servlet.http.HttpServletRequest;

// 升级后
import jakarta.persistence.Entity;
import jakarta.validation.Valid;
import jakarta.servlet.http.HttpServletRequest;

可用 IDE 的全局替换把 javax.persistence 批量改成 jakarta.persistence 等,但要注意不要误伤 javax.xml、javax.crypto 这类仍属于 JDK 标准、不在 Jakarta 迁移范围内的包——它们依然是 javax,改错了反而编译不过。

三、Spring Security 6 配置范式迁移

Spring Security 6 彻底移除了 WebSecurityConfigurerAdapter,改为暴露 SecurityFilterChain Bean;antMatchers 被 requestMatchers 取代,authorizeRequests 改为 authorizeHttpRequests。

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf.disable()) // 无状态 API 常见做法
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/public/**").permitAll()
                .anyRequest().authenticated()
            )
            .sessionManagement(sm ->
                sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
        return http.build();
    }
}

方法级安全(@PreAuthorize 等)现在通过 @EnableMethodSecurity 开启,旧的 @EnableGlobalMethodSecurity 已废弃。如果你用 OAuth2 Resource Server,.oauth2ResourceServer().jwt() 的写法保留,但 JWT 解码器与 CORS 配置要显式声明;密码编码器依旧推荐 BCryptPasswordEncoder。在 Spring 项目里接入大模型做权限或业务编排,可参考我们的 Spring AI 实战:Java 项目如何接入大模型。

四、配置属性与中间件的改名清单

部分自动配置前缀在 3.x 中调整,迁移后若发现配置“不生效”,多半是 key 变了。下面是最常踩的几处:

配置项旧(2.x)新(3.x)说明
Redisspring.redis.*spring.data.redis.*3.x 统一前缀
MongoDBspring.data.mongodb.*spring.mongodb.*3.0 起调整
Actuator 暴露需显式配置默认仅 health/info要额外 expose
Jakarta Validationjavax.validationjakarta.validation命名空间迁移

一个实用技巧:升级后访问 /actuator/configprops(需先 expose)或打开 DEBUG 日志,可以看到哪些配置项被识别、哪些被悄悄忽略,快速定位“配置失效”的元凶。把旧 key 与新 key 的差异整理成一份对照表,是团队批量迁移时最省心的做法。

五、第三方依赖的 Jakarta 化

很多库在旧版本只认 javax,升级后必须换到 Jakarta 兼容版本:MapStruct、Lombok(需 1.18.30+)、QueryDSL、Hibernate 6、Tomcat 10。否则编译或运行时报“javax 找不到”。

<properties>
    <java.version>17</java.version>
    <maven.compiler.source>17</maven.compiler.source>
    <maven.compiler.target>17</maven.compiler.target>
</properties>

<!-- Lombok 必须 1.18.30+ 才支持 Jakarta -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.34</version>
    <scope>provided</scope>
</dependency>

具体清单(均需在升级后换成 Jakarta 兼容版):MapStruct 1.5.5+、Lombok 1.18.30+、QueryDSL 5.1+、Hibernate 6.x、Tomcat 10.1+、EclipseLink 4.x。若项目用了 MyBatis,要确认 mybatis-spring 版本与 Jakarta 兼容。Hibernate 6 本身也随 Jakarta 对齐,HQL 与映射行为有细微调整,务必把集成测试跑一遍再上线。

六、可观测性与测试的变化

Spring Cloud Sleuth 已进入维护模式,分布式链路追踪改用 Micrometer Tracing;测试侧 @SpringBootTest 基本兼容,但 spring-security-test 需同步升级,且 @WithMockUser 用法不变。

升级后用 JFR(Java Flight Recorder) 做一次性能基线对比,确认 GC 与吞吐没有回退,是个稳健的好习惯。日志框架方面 Logback/SLF4J 行为基本不变;若用 Log4j2 请注意升到 2.17+ 安全版本。测试里 MockMvc、TestRestTemplate 仍可用,@MockBean 不变,只是安全相关测试注解要在新版本 spring-security-test 下引入。

七、分阶段迁移与回滚策略

不要一次性在主干改完就发布。建议:先在 CI(如 GitHub Actions 流水线)跑通编译与单测;再发到预发环境做接口回归;最后灰度生产。一旦发现启动失败,立即回退到旧镜像。

# 1. 基于旧分支打一个回滚镜像标签
docker build -t myapp:rollback -f Dockerfile.old .

# 2. 生产发现启动异常,立刻切回旧版本
kubectl set image deployment/myapp myapp=myapp:rollback

# 3. 保留新版本镜像,定位 javax/jakarta 冲突日志
kubectl logs deployment/myapp | grep -i "javax\|ClassNotFound\|jakarta" 

更稳妥的做法是引入特性开关(Feature Flag),把新版本先放量 5% 流量,观测错误率与延迟曲线;配合 CI 矩阵在 PR 阶段就编译 JDK 17 产物,避免“本地能跑、流水线挂”的尴尬。回滚时优先用镜像标签切换而非重新构建,能做到分钟级恢复,把业务影响压到最低。

八、升级后顺手拿下的新特性

升到 3.2+ 后可启用虚拟线程,用极少改动把阻塞 IO 吞吐拉满,详见我们的 Java 虚拟线程:Project Loom 高并发实战。此外 ProblemDetail(RFC 7807)让统一异常响应更规范,GraalVM 原生镜像支持也让冷启动大幅缩短。

ProblemDetail 配合 @ExceptionHandler 可返回结构化错误体,前端无需再解析千奇百怪的错误格式;Native Image 通过 Spring AOT 在编译期处理反射与代理,显著缩短 Serverless 场景冷启动。对于已有 JVM 调优经验(见 JFR 文章)的团队,这些新能力的收益会更明显。

结语

Spring Boot 3 升级的核心就一句话:先解决 JDK 17 基线,再集中剿灭 javax→jakarta 的残留,最后用 CI 灰度兜住回滚。把这三步做扎实,迁移就是一次有收益的常规操作,而不是通宵救火。把上面这份清单存进团队 Wiki,下次升级你只需要照着打勾。

上一篇 Docker Compose 实战:用 docker-compose.yml 编排多容器服务
下一篇 SQLite 实战:轻量嵌入式数据库的并发优化与适用边界
最新评论
Docker 网络模式详解:bridge/host 生产选型 - FSData
Docker 网络模式详解:bridge/host 生产选型 - FSData
8月20日
[…] 注意最后那条:把端口绑定到 127.0.0.1,再由宿主机上的 Nginx 反代进来,比直接 -p 80:80 暴露安全得多,也方便统一做
Vue3 + TypeScript 工程化:类型安全的前端架构 - FSData
Vue3 + TypeScript 工程化:类型安全的前端架构 - FSData
8月20日
[…] Pinia 的工程化用法,可以参考我们之前的实战文章 《Vue3 组合式 API + Pinia:状态管理从入门到实战》,那里覆盖了 Sto
2026 年最值得装的 10 个 VS Code 插件(AI/前端/后端/效率全覆盖) - FSData
2026 年最值得装的 10 个 VS Code 插件(AI/前端/后端/效率全覆盖) - FSData
8月19日
[…] 状态,点一下就能起停容器,配合我们讲过的 Docker Compose 编排 […]
Redis 缓存设计:穿透、击穿、雪崩与最佳实践 - FSData
Redis 缓存设计:穿透、击穿、雪崩与最佳实践 - FSData
8月19日
[…] 缓存三大坑的本质都是”缓存失效导致请求穿透到数据库”。记住口诀:穿透用布隆、击穿用锁、雪崩用错峰。配合合理的过期策略
GitHub Actions 实战:从零搭建 CI/CD 流水线 - FSData
GitHub Actions 实战:从零搭建 CI/CD 流水线 - FSData
8月19日
[…] Docker 镜像部署。结合我们之前写的 Docker Compose 一键编排实战,可以在 CI […]