
Spring Boot 4.0 正式发布的消息刚出来的时候我朋友圈一片哀嚎。倒不是大家不期待新版本而是这个版本的“硬”变化实在太多基线直接压在 Java 17 上整个 Jakarta EE 命名空间换血自动配置机制也做了大范围调整。对于维护过老项目的团队来说“人已麻”三个字太真实了——这意味着几乎每个模块都要重新过一遍编译、测试、替换依赖连多年没动过的启动类都可能要改。但麻归麻事情还是得干。这篇文章我不打算复读官方 Release Notes而是从一个普通后端开发者的视角把 Spring Boot 4.0 里最关键的几个变化、升级前的准备工作、实际操作中的改造点、以及我见过的各种坑整理出来给你一份可以直接照着做的参考。无论你是打算把老项目从 2.x/3.x 迁过来还是新项目直接上 4.0这篇文章都能帮你少走弯路。1. 先说清楚 4.0 到底动了哪些“地基”1.1 Java 17 与 Jakarta EE 的版本墙Spring Boot 4.0 最让人“麻”的第一件事就是它的运行环境基线变成了 Java 17。注意这不是说“推荐用 Java 17”而是说“用低于 17 的 JDK 你连启动都启动不了”。如果你现在还在生产环境跑 Java 8 或者 Java 11那么这个版本对你来说不是“升级”而是一次底层运行时的大换血。与 Java 版本同步提升的还有 Jakarta EE。Spring Boot 3.x 虽然已经切换到 Jakarta 命名空间但 4.0 更进一步把整套规范基线推到了 Jakarta EE 11。这意味着javax.*到jakarta.*的迁移不再是“偶尔遇到一个包名需要改”而是所有涉及 Servlet、Persistence、Validation、Annotation 的代码都必须彻底转向 Jakarta 命名空间。很多老项目里自研的公共组件、工具类、拦截器只要用的是javax.servlet、javax.persistence这一套这次全部要重命名。关键项Spring Boot 3.xSpring Boot 4.0最低 JDKJava 17Java 17推荐 21 或 25 LTSJakarta EE9/1011基础框架Spring Framework 6.xSpring Framework 7.x命名空间javax 已基本退出jakarta 全面接管自动配置条件装配 配置属性模块化重组 新的装配机制1.2 自动配置机制的重构方向用过 Spring Boot 的人都知道自动配置是它的灵魂。以前我们引入一个 Redis、一个 Kafka只要在pom.xml里加依赖再写几行application.yml很多东西就能直接跑起来。这是靠EnableAutoConfiguration和spring.factories/AutoConfiguration.imports一套机制搞定的。Spring Boot 4.0 对这套机制做了进一步模块化。最明显的变化是自动配置类的粒度变得更细、条件装配逻辑更严格官方对“什么时候该加载这个配置”做了更明确的约束。换句话说以前那种“我加个依赖它就把配置全部帮我弄好”的情况变少了有些配置项不再无脑默认开启你必须显式声明意图。这对老项目的影响非常大。最常见的情况是升级到 4.0 之后某个 Bean 突然找不到了某个配置项不生效了但日志里又没有报错。这种“静默失效”是最折磨人的因为它不像编译错误那样直接告诉你哪行写错了。解决这类问题的思路就是去理解自动配置的触发条件而不是继续把它当黑盒。1.3 依赖管理与版本号的跳变动线Spring Boot 4.0 带来的另一个“麻点”是版本号体系的变动。3.x 时代大家已经习惯了3.0.x、3.1.x、3.2.x这样的小步快跑节奏但 4.0 直接跳到了一个大的主版本上。这意味着很多第三方依赖的兼容版本也发生了偏移。举个例子以前你在 Spring Boot 3.2 里用的某个第三方库版本到了 4.0 环境下可能不再被官方 BOM 管理。这时候你自己就要手动指定版本号并确认它跟 Spring Framework 7.x 的 API 是兼容的。尤其是一些跟 Spring 生命周期耦合比较深的库比如安全框架、服务发现客户端、消息中间件客户端它们的适配往往要晚于 Spring Boot 主版本发布一段时间。如果你追求“一发布就升级”你可能会发现自己被卡在某个依赖的兼容性上。1.4 对现有项目的影响半径评估在动手升级之前先要搞清楚“哪些地方一定会改、哪些地方可能改、哪些地方大概率不用动”。根据我多年折腾框架升级的经验影响半径通常这么划分一定会改的地方启动类上的注解和包名引用如果用了 Jakarta 相关注解所有javax.*开头的 import内置服务器、连接池、JSON 序列化等基础组件的版本配置与安全认证相关的配置项如果用了统一的 Security 配置可能会改的地方自定义的自动配置类和条件注解配置文件里被移除或重命名的配置项第三方组件的 API 调用方式例如某些客户端库的构建器变化大概率不用动的地方基于 JPA 的实体类写法只要不涉及包名变化业务层的 Service / Controller 基础代码纯 DTO、工具类、基础 POJO我的建议是升级之前花半天时间用 IDE 全局搜索javax.和已废弃的spring.配置项把它们做成一张清单逐项评估优先级。这一步看起来简单却能省下后面大量的排查时间。2. 升级前的全面体检与清单准备2.1 基线体检JDK 版本、构建工具、容器运行时如果你决定要升级到 Spring Boot 4.0第一步不是改pom.xml而是先检查你的整个运行环境链。只改代码不换环境等于给 Plane 换引擎却忘了检查跑道。先看 JDK。开发机、CI 机器、生产镜像里的 JDK 版本必须统一到 17 以上。如果公司内部还有老项目跑在 Java 8 上那你还要考虑同一台部署机上多个 JDK 共存的问题。我的习惯是在Dockerfile和 CI 配置里直接把基础镜像的 Java 版本锁死避免“本地能跑、服务器上跑不起来”的经典悲剧。再看构建工具。Maven 用户建议升级到 3.9Gradle 用户建议升级到 8.x 或更高。Spring Boot 4.0 的插件对老版本构建工具兼容性有限有时候一个诡异的构建失败追根到底就是构建工具版本太老。最后看容器运行时。如果项目是部署在传统 Servlet 容器里的请把 Tomcat/Jetty 等容器的版本也纳入检查范围。Spring Boot 4.0 默认内嵌的容器版本已经更新如果你用的是外置容器部署war包容器版本太低会直接导致 Servlet 规范不匹配。2.2 依赖树审视和多余模块清理Spring Boot 升级不像普通依赖升级它不是“改一个版本号就能完事”的。很多问题的根源在于依赖之间的传递关系发生了变化。这时候mvn dependency:tree就是你最好的朋友。我在实际操作中会分成三步来走升级前先跑一次mvn dependency:tree把当前依赖树完整导出来保存一份。升级到 4.0 后再跑一次对比两棵树的差异。重点看哪些依赖被移除了、哪些被隐式引入了、哪些版本发生了冲突。这个对比过程能发现很多“莫名其妙”的问题。比如你从某个依赖里传递引入的旧版 Jackson可能会覆盖 Spring Boot 4.0 默认管理的 Jackson 版本导致序列化行为变化。这种情况下你需要在pom.xml里显式声明正确的版本把传递依赖的版本“压”下去。另外Spring Boot 4.0 对一些软件包换了管理方式比如某些 starter 被拆分或合并。如果你引用了老版本里已经不存在或已合并的 starter编译时可能不会报错但运行时某些功能就是起不来。这个时候去看依赖树通常一眼就能发现问题在哪儿。2.3 废弃 API 的“滚雪球”清理长期维护的项目里废弃 API 是滚雪球一样的存在。你今天用的WebMvcConfigurerAdapter明天用的RestTemplate后天用的某个xxTemplate都是在不同版本里被标记过废弃的。平时用着不碍事但大版本升级的时候这些废弃 API 往往会被直接移除或者行为发生破坏性变化。Spring Boot 4.0 对废弃 API 的处理比 3.x 更激进。3.x 时代给你留了缓冲期打出deprecated警告还可以继续跑4.0 里很多直接就不给你留了。升级时编译报错你能看到一堆“cannot find symbol”的提示而这些符号大概率都是你拖了很久没清理的废弃方法。我的建议是升级前在项目里全局搜索Deprecated相关依赖项把你自己代码里用到的地方先做一轮替换。比如RestTemplate换成RestClient各种自定义WebMvcConfigurer的旧写法换成新函数式风格。这个工作很琐碎但它能避免你升级后面对几百个编译错误无从下手。2.4 测试策略和灰度发布很多人升级框架时只关注能不能编译、能不能启动却忽略了一个更重要的环节测试。Spring Boot 4.0 底层变化太大编译通过和启动成功只能说明“基础没塌”不能说明业务逻辑都正常。我比较推荐的策略是“新旧并行 灰度流量验证”。在升级过程中保留一个旧版本的完整部署环境新版本环境先接入测试流量和灰度流量跑一段时间的自动化测试和线上镜像流量后再逐步切真实用户。这比“一个晚上升级完、第二天线上出问题”要稳妥得多。测试用例本身也要调整。SpringBootTest这类集成测试在 4.0 里的启动行为可能会有变化尤其是涉及到嵌入式服务器和测试容器的场景。建议先只跑核心的单元测试和少量冒烟测试等稳定后再放全量回归。不要指望一把梭。3. 升级实操关键改造点与踩坑实录3.1 依赖与构建配置的改动思路到了实际操作阶段最先动手的就是pom.xml。如果你是从 Spring Boot 3.x 升级改动相对温和一点如果你还在 2.x那就不是“升级”而是“迁移”了。我们先说大多数团队会面对的场景从 3.x 升级到 4.0。最核心的改动是替换父 POM 版本。把spring-boot-starter-parent的版本从3.x.x改成4.0.x然后重新导入依赖。这一步会在 IDEA 或 IDE 里触发大规模的依赖解析变化第一次跑可能非常慢而且极大概率会遇到某个依赖下载失败或冲突。别急这是正常的。我给一个很笨但很有效的做法升级时不要一次性把所有依赖都改成最新版只改必要的。先把 Spring Boot 主版本号升上去其他第三方依赖先保持不变直到编译报错时再去逐个调整。这样做的好处是你能明确知道到底是哪个依赖的问题而不是一堆问题混在一起无从下手。parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version4.0.x/version relativePath/ /parent注意4.0.x里具体用哪个次版本要看你们团队的节奏。通常我建议等.1或.2出来之后再升级主版本因为.0版本多多少少会有一些底层依赖的适配问题。如果你追求稳定可以参考开源社区里各个框架的兼容矩阵再做决定。3.2 自动配置迁移的常见形态升级后最容易出问题的就是自定义自动配置。很多中大型项目都有自己封装的xxx-starter内部用AutoConfiguration.imports注册配置类。Spring Boot 4.0 对这批文件的处理逻辑虽然有兼容但加载顺序和条件判断语义可能发生变化。我自己遇到最典型的情况是某个自定义 starter 里的ConditionalOnMissingBean突然失效了。以前只要容器里没有同类 Bean就自动装配一个默认实现升级之后默认实现竟然和用户显式声明的 Bean 同时存在导致行为混乱。排查到最后发现是自动配置类的加载顺序变了本来应该先加载用户定义的 Bean结果现在自动配置类先跑了。解决方案是在自定义 starter 里显式声明AutoConfigureOrder和AutoConfigureAfter把加载顺序固定下来。这个小细节在之前的版本里一般不用管但 4.0 的语义更严格你必须把“顺序”当作一等公民来对待。另外配置属性的绑定方式也值得注意。4.0 里ConfigurationProperties对构造方法绑定和校验的默认策略有调整。以前你可能只写了 setter 让它慢慢绑现在有些场景要求更明确的绑定方式。建议升级后跑一遍所有“配置项加载”相关的测试确认每个配置组都能正确映射。3.3 可观测性端点的适配Spring Boot 3.x 引进了 Micrometer Tracing 等一套可观测体系。4.0 在这块继续做“加法”默认暴露的端点更多指标更细但与此同时你之前自定义的一些按management.*开头的配置项可能会失效。这会让运维和开发都很麻因为监控数据一旦对不上线上出了问题你根本没法快速定位。我的经验是升级后先检查/actuator/health、/actuator/metrics这组基础端点是否正常响应再看自定义的指标是否还能上报到监控系统。如果发现指标不齐优先查management.endpoints.web.exposure配置是否被新版的默认值覆盖。还有一点4.0 对 OpenTelemetry 的支持更深入了。如果你之前是自建了一套 trace 上报逻辑现在要考虑是不是应该直接切到标准化的 OTel SDK 上。这看起来是额外工作但长期来看能减少很多重复轮子。3.4 从 2.x 直接跳跃到 4.0 的特殊风险如果你还在 2.x想着“一步到位直接升到 4.0”我的建议非常明确不要这么做。2.x 到 4.0 的区别就像是旧的木质帆船直接换成现代驱逐舰中间隔着 3.x 这个完整的过渡期跳过的不仅仅是版本号还有大量的语义变化。非要硬上的话你面对的将是javax到jakarta的全量包名替换涉及面极广Security 配置 DSL 结构完全不同很多配置属性的 key 彻底消失或改名测试体系和 mocking 机制的默认行为变化第三方依赖的兼容版本几乎全部需要重新评估一次性承受这么多变化即使是经验丰富的开发者也很难在短时间内稳定交付更何况大多数团队没有那么多精力专门做这种大迁移。所以如果你在 2.x规划时一定要分阶段先升 3.x跑稳了之后再考虑 4.0。4. 常见问题速查与排查技巧4.1 一启动就报 NoClassDefFoundError升级后启动应用最经典的问题就是各种NoClassDefFoundError和ClassNotFoundException。这个错误本身就是一句话你的运行时 classpath 里缺了某个类。但为什么明明依赖都加进去了还是会缺类呢直接去找依赖树。八成是某个第三方依赖把传递依赖“甩”了导致你原来靠它传递引入的类现在没有了。另外也要检查是不是有多个版本的 jar 包冲突类加载器加载的是旧版本而旧版本里没有新代码里需要的新类。我的习惯是用 dependency 插件打印冲突项然后在pom.xml中显式排除不需要的传递依赖。有些时候你还需要用mvn dependency:analyze来分析哪些依赖是声明了但没用、哪些是用了但没声明。这个工具在升级场景下特别好用能自动帮你找出潜在的问题。4.2 配置项失效与迁移属性另一个高频问题是项目能启动但某些配置项明显不生效。比如日志级别没变化、连接池参数没变化、缓存策略没变化。出现这种情况多半是配置项的 key 在 4.0 里被改名了。Spring Boot 官方文档里有一份配置属性迁移对照表。从 3.x 到 4.0有几个常见的改动方向server.*相关端口和连接配置整体梳理部分并发参数改名spring.datasource.*里连接池专属参数被拆分到各自的池子配置下management.*下的暴露策略默认值调整spring.redis.*与新的客户端适配器参数整合遇到配置不生效我的做法是先用启动日志里的“配置报告”功能看看哪些属性被实际绑定到了哪些 Bean 上。Spring Boot 4.0 对配置绑定的可观测性做得更好你可以通过 Actuator 的configprops端点直接查看每个配置属性当前的真实值。这个比翻文档猜 key 要高效得多。4.3 测试容器与虚拟线程的兼容Spring Boot 4.0 对虚拟线程的支持进一步完善很多项目开始用虚拟线程来处理高并发请求。但虚拟线程有一个新的问题它跟某些测试容器框架的兼容性并不完美。比如一些基于传统线程局部变量设计的测试工具在虚拟线程环境下会出现不可预期的行为。我的建议是如果项目里用了虚拟线程升级后的集成测试一定要在真实环境下跑一遍不要只依赖spring.test提供的 mock 功能。重点观察线程相关的隔离性特别是数据库事务和缓存上下文的传播机制。如果出现“这个测试单独跑能过、全部跑就挂”的现象大概率跟测试线程模型有关。4.4 性能回归排查每次大版本升级性能和资源占用都是玄学般的领域。Spring Boot 4.0 因为引入了更多模块化和防御性逻辑在少数场景下启动时间和内存占用会比 3.x 略高。如果你升级后发现生产环境的响应变慢了先不要慌按下面几步去排查先对比启动时间。用-Xlog:classload或者 Actuator 里的启动指标看类加载数量。再看 GC 日志。JDK 17 的 G1 参数和旧版本可能有差异。确认自动配置是否加载了多余的组件比如你连 Redis 都不用结果因为某个 starter 引入了 Redis 自动配置。最后压测对比看是不是某个默认线程池参数变了。性能问题有一点很关键不要凭感觉猜测一定要有前后对比数据。升级之前就把旧版本的接口响应时间和 CPU 内存指标记录下来升级后做同场景对比哪差补哪否则很可能是白忙一场。这里也补充一个我实际遇到过的案例某公司在升级后用 JFR 抓取生产样本发现大部分耗时都集中在日志框架的异步队列上。查了半天不好定位最后发现是配置项改名后原来的日志队列容量设置没有从旧 key 迁移过来导致默认容量太小、频繁触发丢弃策略。这是那种“不看到配置报告根本想不到”的问题。4.5 典型案例复盘一个跨平台系统模拟迁移过程前面讲了很多零散的点最后我用一个模拟项目 X一个典型的跨平台系统的迁移过程来串一下整体思路。这个模拟项目的核心技术栈是Spring Boot 3.2 Java 17 MyBatis Redis Kafka Spring Security。升级目标Spring Boot 4.0。我们按顺序做了这么几步全局搜索javax.把所有实体、接口、工具类里的老包名全部替换成jakarta.。这一步其实还好IDE 的全局替换能加速完成。升级pom.xml中的父版本号然后跑mvn compile。第一轮报错主要集中在 Security 配置和自定义 starter 的自动配置上。Security 配置改动最费时间。4.0 对 Security 过滤器链的行为做了调整以前通过继承适配器写的一堆方法现在要改成组件式写法整个过程相当于重写了一遍安全配置。自定义 starter 里的自动配置类加载顺序出了问题通过加AutoConfigureAfter解决。测试阶段发现 Kafka 消费者线程模型和虚拟线程环境有冲突通过限定部分线程剥离策略解决。灰度验证一周后全量切换。这个过程中前后用了大约两周其中实际编码不超过三天剩余时间全在测试和灰度验证上。所以给所有想升级的团队一个忠告升级 Spring Boot 主版本主要工作量不在写代码而在验证。把时间预算留足不要用一个周末去赌“应该没问题的吧”。5. 关于 4.0 的一些个人体感说回标题。“人已麻”这个情绪我很能理解但作为过来人我想说句实在话每次 Spring 大版本升级都会引发一次技术圈的情绪波动但你回头看历史Spring Boot 1.x 到 2.x、2.x 到 3.x哪次不是一开始满屏吐槽几个月后大家该用还是用技术演进的方向不会因为“麻”而回头。Spring Boot 4.0 的核心方向其实非常明确拥抱新 Java、强化可观测性、推进模块化。对老项目来说它确实带来迁移成本对新项目来说它提供了更清晰的工程基线。所以我的建议是不要急着在生产环境上 4.0但也不要完全无视它。先用一个非核心服务做技术验证跑通以后把这个版本真正吃透再逐步推广到全团队。最后分享一个对所有人都有用的小技巧升级前先在本地开一个空白的 Spring Boot 4.0 项目把你们项目里的核心依赖一个一个加进去。每加一个就启动一次看到底是哪个依赖引发问题。这种方法比直接在老项目上动手“一刀切”要可控得多尤其是当你的项目里有一堆乱七八糟的历史依赖时它能帮你快速定位“麻”的源头把不可控的升级变成一步步可验证的渐进过程。