
如果你最近也在刷开发者社区大概率看到过“Tibo 发文引热议”的消息。关于那篇文章里具体争论什么这里不展开聊但背后有一个问题很值得认真思考一个项目到底什么时候才算“是时候了”进行技术升级这两年问得最多的就是 Spring Boot 2.7 要不要升到 Spring Boot 3升了会不会踩坑不升又会不会被依赖和安全问题拖住。与其一直停留在“要不要升”的纠结里不如把整套迁移思路完整拆开从核心概念、环境准备、代码改动到排错清单一次性理清楚。本文会以一个常见的 Spring Boot 2.7 项目为例按照真实迁移顺序演示如何升级到 Spring Boot 3.2并覆盖 Java 17、Jakarta EE、Spring Security 6、配置属性变化、第三方依赖兼容性等关键点。无论你是后端开发、项目负责人还是准备在简历里加上“Spring Boot 3 迁移经验”的学习者这篇文章都能给你一套可直接落地的参考方案。1. 背景与核心概念1.1 Spring Boot 3.0 解决了什么问题Spring Boot 3.0 是一个跨度非常大的版本底层基于 Spring Framework 6.0最低要求 JDK 17。它带来了一大波新能力全面拥抱 Jakarta EE将原有的javax.*命名空间升级为jakarta.*。支持 Native Image可以结合 GraalVM 将应用编译成原生可执行文件启动速度和内存占用都有明显优化。更好的可观测性支持引入 Micrometer Tracing。基础依赖同步升级比如 Tomcat 10、Hibernate 6、Spring Security 6。很多团队一直停留在 Spring Boot 2.3、2.5 或 2.7主要是因为升级风险大、涉及面广。但 Spring Boot 2.7 已经是 2.x 的最后一个功能分支社区支持和维护会逐渐收紧。对于长期维护的项目来说“是时候了”并不是头脑发热而是因为老版本会慢慢变成安全短板和兼容性瓶颈。1.2 升级 Spring Boot 3 的核心收益从实际项目角度看升级带来的收益主要有三类收益类别说明安全与维护Spring Boot 2.x 停止 OSS 支持后漏洞修复和版本更新频率会下降升级是降低风险的手段性能与体验新版本启动速度更快对容器化部署更友好内存占用有优化空间新特性使用想用虚拟线程、Native Image、更灵活的可观测性必须以 Spring Boot 3 为基础不过收益不是白来的。升级意味着构建文件、代码包名、安全配置、第三方依赖都需要调整。如果项目里使用了老旧的 MyBatis、ShardingSphere、Druid 等组件还需要逐一确认兼容版本。1.3 升级前需要想清楚的问题在开始动手之前建议先问自己四个问题线上系统有多少个微服务全部升级还是一部分先试点项目中有没有直接依赖javax.*的老代码或自定义 starter第三方中间件有没有 Jakarta 兼容版本核心业务有没有完善的自动化测试可以兜底这四个问题决定了升级策略是“激进式全量升级”还是“灰度式分批升级”。大多数情况下更推荐后者。2. 环境准备与版本说明2.1 版本要求在开始升级之前先确认本机环境是否满足 Spring Boot 3 的基本要求组件要求说明JDK17 或更高推荐 17 LTS也可以使用 21 LTSMaven3.6.3需要支持新版插件Gradle7.5如果使用 Gradle版本不能太低目标 Spring Boot3.x本文示例以 3.2.x 为例需要注意的是不同 Spring Boot 3.x 小版本对 JDK 的支持范围有差异。请以官方文档对应版本为准。实际项目中不要盲目追最新优先选择已经发布一段时间、社区反馈比较稳定的版本。2.2 示例项目结构为了演示方便我准备了一个简单的用户管理项目技术栈为Spring Boot 2.7.18Spring WebSpring Data JPASpring SecurityH2 内存数据库项目结构如下upgrade-demo ├── pom.xml └── src ├── main │ ├── java │ │ └── com │ │ └── example │ │ └── demo │ │ ├── DemoApplication.java │ │ ├── config │ │ │ └── SecurityConfig.java │ │ ├── controller │ │ │ └── UserController.java │ │ ├── entity │ │ │ └── User.java │ │ ├── repository │ │ │ └── UserRepository.java │ │ └── service │ │ └── UserService.java │ └── resources │ └── application.yml └── test └── java └── com └── example └── demo └── DemoApplicationTests.java生产环境中的数据源、Redis、消息队列等配置会比这里复杂但迁移思路是通用的。2.3 升级前的备份与分支管理升级属于高风险变更千万不要直接在主干分支上随意修改。建议从当前主干拉出一个独立分支git checkout -b feature/springboot3-upgrade同时保留当前可运行版本的标签方便回退git tag release-springboot-2.7.18如果项目通过私有 Nexus 管理依赖升级前最好确认目标版本和第三方兼容版本已经同步到私服。3. 核心变化点拆解3.1 Java 17 与 Jakarta EE从 javax 到 jakartaSpring Boot 3 最重要的底层变化是从 Java EE 切换到了 Jakarta EE 9。最直观的改动就是项目里大量import javax.*变成了import jakarta.*。常见的包名变化如下旧包名新包名javax.persistence.*jakarta.persistence.*javax.servlet.*jakarta.servlet.*javax.validation.*jakarta.validation.*javax.annotation.*jakarta.annotation.*如果项目里用了 Servlet 过滤器、自定义校验注解、JPA 实体都需要全局搜索替换。建议先用 IDE 的全局搜索确认影响范围搜索内容javax.persistence 搜索范围所有文件然后再手动替换。不要直接全局无脑替换有些第三方依赖内部仍可能引用旧包名需要同步升级依赖版本。3.2 Spring Security 6告别 WebSecurityConfigurerAdapterSpring Security 6 是 Spring Boot 3 里让很多人头疼的一部分。旧版写法Configuration EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers(/public/**).permitAll() .anyRequest().authenticated(); } }升级后WebSecurityConfigurerAdapter已经被移除。新的写法是基于SecurityFilterChain的组件式配置Configuration EnableWebSecurity public class SecurityConfig { Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .csrf(csrf - csrf.disable()) .authorizeHttpRequests(auth - auth .requestMatchers(/public/**).permitAll() .anyRequest().authenticated()) .httpBasic(Customizer.withDefaults()); return http.build(); } }这里的关键变化authorizeRequests()改成了authorizeHttpRequests()。antMatchers()改成了requestMatchers()。使用 Lambda DSL 风格配置。如果不使用 CSRF 防护需要显式关闭。这样的设计更符合 Spring 的推荐做法也避免了对全局配置类的继承耦合。3.3 配置属性变化与自动检测Spring Boot 3 对很多application.properties或application.yml配置项做了梳理。有些属性被重命名有些被删除有些只是迁移到了更清晰的命名空间。比较常见的像# 旧写法 management.metrics.export.prometheus.enabledtrue # 新版本部分属性发生变化需要根据版本确认 management.prometheus.metrics.export.enabledtrue为了避免手工排查遗漏Spring Boot 官方提供了一个迁移辅助依赖dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-properties-migrator/artifactId scoperuntime/scope /dependency添加这个依赖后应用启动时或配置解析时会打印属性迁移提示帮助你发现已失效的配置项。升级完成后记得把这个依赖从pom.xml中移除。3.4 第三方依赖兼容性很多项目升级卡住不是 Spring Boot 本身的问题而是第三方 starter 没有跟上 Jakarta 命名空间。例如MyBatis 的 starter 需要升级到支持 Spring Boot 3 的版本。Druid 连接池需要确认是否支持jakarta.*。ShardingSphere 需要选择兼容 Spring Boot 3 的分支。老版本 Flyway 需要升级到支持 Spring Boot 3 的版本。这里无法列出固定版本号因为版本变化太快不同项目实际使用的版本也不同。最稳妥的方法是去对应开源项目的官方兼容矩阵中确认优先选择明确标注支持 Spring Boot 3 / Jakarta 的版本。4. 完整实战案例从 Spring Boot 2.7 迁移到 3.2下面开始走一遍完整的迁移过程。4.1 修改构建文件首先修改pom.xml中的 Spring Boot 版本parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.2.1/version relativePath/ /parent同时把 Java 版本调整为 17properties java.version17/java.version /properties如果你使用 Gradle对应修改plugins { id org.springframework.boot version 3.2.1 id io.spring.dependency-management version 1.1.4 id java } java { toolchain { languageVersion JavaLanguageVersion.of(17) } }注意Spring Boot 3 要求父 POM 或 BOM 的版本不能低于 3.0.0。如果项目里是通过自定义父 POM 引入依赖管理需要确认spring-boot-dependencies的版本也一起更新。4.2 修改启动类和实体类启动类通常不需要大改// 文件路径src/main/java/com/example/demo/DemoApplication.java package com.example.demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }实体类中需要替换 JPA 依赖的包名// 文件路径src/main/java/com/example/demo/entity/User.java package com.example.demo.entity; import jakarta.persistence.*; Entity Table(name users) public class User { Id GeneratedValue(strategy GenerationType.IDENTITY) private Long id; private String name; public User() { } public User(String name) { this.name name; } public Long getId() { return id; } public void setId(Long id) { this.id id; } public String getName() { return name; } public void setName(String name) { this.name name; } }如果你的项目里还使用了javax.validation.constraints.*也要统一替换为jakarta.validation.constraints.*。4.3 编写 Controller、Service、RepositoryController、Service、Repository 这三层在大多数情况下可以保持原有代码结构。UserController// 文件路径src/main/java/com/example/demo/controller/UserController.java package com.example.demo.controller; import com.example.demo.entity.User; import com.example.demo.service.UserService; import org.springframework.web.bind.annotation.*; import java.util.List; RestController RequestMapping(/api/users) public class UserController { private final UserService userService; public UserController(UserService userService) { this.userService userService; } GetMapping public ListUser list() { return userService.list(); } PostMapping public User create(RequestBody User user) { return userService.save(user); } }UserService// 文件路径src/main/java/com/example/demo/service/UserService.java package com.example.demo.service; import com.example.demo.entity.User; import com.example.demo.repository.UserRepository; import org.springframework.stereotype.Service; import java.util.List; Service public class UserService { private final UserRepository userRepository; public UserService(UserRepository userRepository) { this.userRepository userRepository; } public ListUser list() { return userRepository.findAll(); } public User save(User user) { return userRepository.save(user); } }UserRepository// 文件路径src/main/java/com/example/demo/repository/UserRepository.java package com.example.demo.repository; import com.example.demo.entity.User; import org.springframework.data.jpa.repository.JpaRepository; public interface UserRepository extends JpaRepositoryUser, Long { }从代码层面可以看到升级的核心并不在业务代码而在于依赖版本和框架 API 的变化。4.4 重写 Spring Security 配置把原来继承WebSecurityConfigurerAdapter的配置类删掉替换为基于SecurityFilterChain的写法// 文件路径src/main/java/com/example/demo/config/SecurityConfig.java package com.example.demo.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.web.SecurityFilterChain; Configuration EnableWebSecurity public class SecurityConfig { Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .csrf(csrf - csrf.disable()) .authorizeHttpRequests(auth - auth .requestMatchers(/public/**).permitAll() .requestMatchers(/api/users/**).authenticated() .anyRequest().permitAll()) .httpBasic(Customizer.withDefaults()); return http.build(); } }这里用requestMatchers替代了antMatchers同时把 URL 匹配规则集中在一个authorizeHttpRequests中整体阅读起来更清晰。4.5 修改 application.yml接下来调整配置文件。示例配置如下# 文件路径src/main/resources/application.yml server: port: 8080 servlet: context-path: /demo spring: application: name: upgrade-demo datasource: url: jdbc:h2:mem:testdb driver-class-name: org.h2.Driver username: sa password: jpa: hibernate: ddl-auto: create-drop show-sql: true management: endpoints: web: exposure: include: health,info,metrics如果项目原先配置了spring.datasource.driver-class-name为com.mysql.jdbc.Driver升级时注意驱动类名可能已经变化。比如 MySQL 官方新版驱动推荐使用spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver如果你的项目是纯 H2 示例则不需要额外处理。4.6 处理第三方依赖在示例项目中假设没有复杂第三方依赖因此升级后依赖比较简单。实际项目中第三方依赖的处理是最容易出现意外的地方。比如老版本的 MyBatis 启动器dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version2.3.1/version /dependency这类旧版本可能无法直接兼容 Spring Boot 3。需要去 MyBatis 官方社区查看支持 Spring Boot 3 的 starter 版本升级后再重新编译。4.7 运行与验证完成上述修改后可以先执行编译mvn clean compile如果编译通过再执行测试mvn test最后运行应用mvn spring-boot:run启动成功后访问接口查询用户列表GET http://localhost:8080/demo/api/users健康检查GET http://localhost:8080/demo/actuator/health由于接口上有安全配置未带认证信息访问/api/users会返回 401。因为 H2 内存数据库会在应用启动时初始化表结构所以可以先调用POST接口新增用户再调用GET接口查看数据。如果你看到类似下面的输出说明应用已经正常启动Tomcat started on port 8080 (http) with context path /demo Started DemoApplication in 2.5 seconds4.8 使用迁移辅助工具除了手动修改Spring 官方还提供了一些迁移辅助工具例如 Spring Boot Migrator。这类工具可以扫描老项目并尝试自动完成部分迁移。不过自动化工具处理不了所有场景尤其是复杂的安全配置和第三方依赖。建议把工具当作辅助最终还是要靠人工 review 和测试兜底。5. 常见问题与排查思路升级过程中最容易遇到下面这些报错这里整理成一份排错清单。问题现象常见原因解决思路ClassNotFoundException: javax.servlet.Filter依赖引用了旧javax包全局搜索javax.servlet改为jakarta.servlet并升级相关依赖NoClassDefFoundError: WebSecurityConfigurerAdapterSpring Security 6 已移除旧适配器改为使用SecurityFilterChainBean应用启动失败提示PathPattern匹配问题Spring Boot 3 默认使用PathPatternParser检查 Controller 和 SecurityConfig 中 URL 通配符推荐使用/**数据库方言错误Hibernate 6 方言配置变化删除手动配置的 dialect让 Hibernate 自动识别MyBatis 相关 Bean 无法注入MyBatis starter 版本太旧升级到支持 Spring Boot 3 的版本配置属性提示Unknown propertySpring Boot 3 清理了老属性添加spring-boot-properties-migrator定位按提示迁移java.lang.reflect.InaccessibleObjectExceptionJDK 17 模块限制优先升级依赖不要一上来就加--add-opens启动耗时变长或部分 Bean 加载失败依赖注入或自动配置类被误替换查看启动日志中的BeanCreationException堆栈定位到具体配置在实际项目里最常见的情况不是某个大坑而是各种小问题叠加在一起。所以升级过程中要保持耐心建议每改完一部分就编译一次不要等到最后一起解决所有报错。6. 最佳实践与工程建议6.1 升级前先做依赖清单盘点花半天时间把项目里所有依赖梳理出来重点标记三类官方维护的 Spring Boot starter。第三方 starter 和 SDK。项目内部公共模块。这样升级时就能快速判断哪些依赖需要同步升级哪些依赖可能没有兼容版本。6.2 使用灰度发布不要一把梭线上系统升级不建议一次全部替换。可以把 1 到 2 个非核心服务先升级观察运行稳定性和性能指标。确认没问题后再逐步扩大范围。如果服务通过 Nacos、Consul 等服务注册中心管理可以结合流量权重做灰度。例如先切 10% 流量到新版本观察错误率、耗时、GC 情况。6.3 测试要提前准备升级前先确认测试覆盖率。重点测试登录认证和权限控制链路。用户核心读写链路。定时任务和消息消费链路。对外提供的 OpenAPI 接口。测试不一定要多但核心链路必须能自动化回归。否则升级后出现问题很难判断是代码问题还是配置问题。6.4 理解并利用迁移辅助依赖在升级过程中可以在pom.xml临时加入dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-properties-migrator/artifactId scoperuntime/scope /dependency启动时它会提示哪些属性已经变化或是无效。定位并修复所有提示后移除这个依赖避免把调试信息带到生产环境。6.5 关注安全与权限变更Spring Security 6 的默认行为比旧版本更严格。尤其在 CSRF、跨域配置、请求匹配规则上不要为了通过测试而随意关闭安全限制。生产环境使用最小权限原则对外暴露的接口尽量显式配置白名单不要使用anyRequest().permitAll()覆盖所有路径。6.6 保留回滚方案升级部署前除了常规备份还需要保留旧版本的可运行产物。建议在发布流程中加上回滚按钮或快速回退脚本。回滚不仅依赖代码还依赖数据库变更。如果升级过程中有增量 SQL 脚本要评估向下兼容性避免回滚后数据库结构和旧代码不匹配。7. 总结与学习路线Spring Boot 3 迁移并不可怕真正需要重视的是变化点梳理和回归验证。本文通过一个完整的示例项目演示了从 Spring Boot 2.7 升级到 3.2 的核心步骤覆盖了 Java 17、Jakarta EE、Spring Security 6、配置属性迁移和第三方依赖兼容性等关键内容。完成基础迁移后下一步可以继续学习Spring Security 6 的授权语义和过滤器链机制。Spring Boot 3 对 GraalVM Native Image 的支持。JDK 21 虚拟线程在 Spring Boot 3.2 中的实践。Micrometer Tracing 与可观测性体系搭建。对于实际项目建议优先关注安全配置、中间件兼容性和回滚预案不要只盯着版本号。升级本身不是目标让项目更稳定、更可持续演进才是“是时候了”的真正含义。如果这篇文章对你有帮助可以先收藏备用。动手迁移时如果遇到新的问题欢迎在评论区一起讨论。