最近在社区里看到一个讨论,说用 Spring Initializr 新建项目时,发现 Spring Boot 3.x 默认要求 JDK 17 起步。不少开发者第一反应是:“我本地环境还是 JDK 8,生产环境也是 JDK 11,这又是框架团队在‘自嗨’吗?”

这种反应很真实。对于一个稳定运行的系统,升级基础运行环境(JDK)往往是成本最高、风险最大的一环。它不像引入一个新库,改几行代码就能用。JDK升级牵涉到编译、测试、部署、监控乃至第三方依赖的兼容性,是牵一发而动全身的“基建”动作。所以,当看到 Spring Boot 这个“应用层”框架,开始“强制”要求使用一个较新的 JDK 时,产生“是不是又在折腾我们”的疑问,非常正常。

但如果我们只停留在“框架团队爱折腾”这个情绪层面,就错过了一次理解技术栈演进底层逻辑的机会。Spring Boot 将最低 JDK 要求定为 17,远非一次简单的版本号跃进。它背后是一系列技术债务的清算、现代开发体验的引入,以及为未来几年开发者生产力所做的铺垫。这不是“自嗨”,而是一次经过权衡的、指向明确的“断舍离”。

1. 为什么这次升级感觉“阵痛”特别大?

要理解这次升级,得先看看我们是怎么一步步走到今天的。很长一段时间里,Java 8 是事实上的“标准”。LTS(长期支持)版本的发布节奏改变了一切。JDK 11 是一个重要的 LTS,但很多团队从 8 升级到 11 时,可能只解决了编译问题,并未充分享受新版本的语言特性和性能红利。紧接着,JDK 17 作为下一个 LTS 在 2021年发布。

Spring Boot 3.0 在 2022年底发布,将基线从 JDK 17 开始,这中间有一个关键的时间差和心理差。

1.1 从“追赶新特性”到“偿还技术债” 在 JDK 8 到 17 的漫长征途中,Java 语言和 JVM 本身发生了深刻变化。这些变化很多不是“锦上添花”的功能,而是对陈旧、低效甚至不安全的模式的彻底革新。例如:

  • 模块化(JPMS) :从 JDK 9 引入,旨在解决 JAR Hell 和定义清晰的模块边界。虽然采用过程缓慢,但它代表了大型应用架构的未来方向。
  • GC 的持续演进 :ZGC 和 Shenandoah 等低延迟垃圾收集器的成熟,让 Java 在响应式、大数据量场景下的表现脱胎换骨。
  • 安全性增强 :不断强化的加密算法、默认的安全策略,以及对旧版弱安全协议的淘汰。

Spring Boot 团队如果要全面支持并优化这些新特性,同时还得兼容旧版 JDK(如8或11)上这些特性不存在或行为不一致的情况,其测试矩阵和维护成本会呈指数级增长。将基线定为 JDK 17,实质上是 将社区的技术栈基线对齐到一个现代、安全、功能完整的起点上 ,甩掉了沉重的历史包袱。

1.2 工具链与体验的“代际差” 开发者体验的差距比想象中更大。在 JDK 17 上,你可以使用 var 关键字编写更简洁的代码,使用 Records 定义不可变数据载体,用 Text Blocks 优雅地处理多行字符串,用 Pattern Matching for instanceof 和 switch 简化条件逻辑。这些不仅仅是语法糖,它们能减少样板代码,降低错误率,提升代码的可读性和可维护性。

当框架本身(如 Spring)开始大量使用这些新特性来重构其内部代码,以追求更清晰的架构和更好的性能时,它要求使用者(开发者)的编译环境也必须跟上。否则,你连阅读框架源码都会遇到障碍,更谈不上深入理解和排查问题了。

1.3 生态的集体转向 一个框架的决策从来不是孤立的。查看近年来主流开源库的更新日志,会发现对 JDK 11 甚至 17 的最低要求越来越常见。构建工具(如 Maven/Gradle 插件)、测试框架、监控代理(如 SkyWalking, Pinpoint)都在逐步提升对高版本 JDK 的支持和优化。Spring Boot 作为整合者,它的基线升级,某种程度上也是 整个 Java 生态向前推进的一个标志和催化剂 。继续停留在旧版本,意味着你正在逐渐远离生态的活跃维护区,未来在集成新工具、修复安全漏洞时会越发困难。

2. 直面升级挑战:不只是改个版本号

理解了“为什么”,接下来就要解决“怎么办”。将现有项目从 JDK 8/11 升级到 17,远不止在 pom.xml 或 build.gradle 里改个版本号那么简单。它是一个系统工程,需要有条理地推进。

2.1 环境隔离与多版本管理 首先,在开发机上管理多个 JDK 版本应成为标配。不要直接替换系统默认的 JDK。

  • macOS/Linux :使用 jenv 或 SDKMAN! 可以非常方便地切换不同版本。
  • Windows :可以手动设置 JAVA_HOME ,或使用类似工具。确保 IDE(如 IntelliJ IDEA)中的项目 SDK 和模块 SDK 都指向正确的 JDK 17。

2.2 依赖兼容性大排查 这是升级过程中最耗时、也最容易出问题的环节。你需要系统性地检查所有依赖。

  1. 构建工具报告 :运行 mvn dependency:tree 或 gradle dependencies ,生成完整的依赖树。
  2. 逐项审查 :对每个直接和间接依赖,去其官方仓库(如 Maven Central)查看最新版本对 JDK 的兼容性说明。重点关注:
    • 核心框架 :Spring Framework, Spring Data, Spring Security 等是否与 Spring Boot 3.x 匹配。
    • 数据库驱动 :MySQL Connector/J, PostgreSQL JDBC Driver 等。
    • 连接池 :HikariCP。
    • 序列化/工具库 :Jackson, Guava, Apache Commons 等。
    • 本地库依赖 :例如通过 JNI 调用的库,必须确认其提供了兼容 JDK 17 的二进制包。
  3. 替代方案 :对于明确不兼容且无人维护的旧库,必须寻找替代品。这是推动项目架构焕新的好时机。

2.3 代码层面的适配与重构 即使依赖都兼容,你的代码也可能需要调整。

  • 移除已废弃的 API :JDK 17 中,很多在早期版本被标记为 @Deprecated 的类和方法已被移除。例如, SecurityManager 相关的部分 API、某些 sun.misc.* 包下的内部类。编译错误会明确指出,需替换为新的标准 API。
  • 模块化(可选但建议了解) :如果你的项目是大型应用,可以考虑开始尝试 JPMS。对于大多数 Spring Boot 应用,可以暂时继续使用“未命名模块”,但需要知道如果依赖的 JAR 开始声明模块,可能会带来一些访问权限问题,通常可以通过启动参数 --add-opens 来解决。
  • 拥抱新语法(循序渐进) :不必一次性重写所有代码。可以在新增代码或修改旧代码时,逐步采用 var , Records 等新特性。这能持续带来代码质量的正向提升。

2.4 测试与监控的全面保障 升级后,测试是确保系统稳定的生命线。

  1. 单元测试 :确保所有用例通过,这是基础。
  2. 集成测试 :覆盖核心业务流程和外部依赖(数据库、缓存、消息队列)的交互。
  3. 性能基准测试 :比较升级前后关键接口的响应时间和吞吐量。虽然期望是提升,但也需排除因 GC 策略变化等导致的意外性能回退。
  4. 生产环境灰度 :如果可能,通过金丝雀发布或蓝绿部署,先将流量导入少量新版本实例,密切监控 JVM 指标(GC 时间、频率、内存使用率)、应用日志和业务指标。

3. 从“被迫升级”到“主动获益”的思维转变

完成升级只是第一步,更重要的是如何利用新版本的能力,让这次投入产生长期回报。这需要我们从“应付框架要求”的被动心态,转向“利用新平台能力”的主动建设。

3.1 开发效率的实质提升

  • Record 简化数据对象 :用于定义 HTTP 请求/响应体、DTO、配置类等纯数据载体,能自动生成 equals() , hashCode() , toString() 和构造方法,代码量减少 80% 以上,且意图极其清晰。
    // 旧方式:冗长的 Lombok @Data 类或手动编写
    // 新方式:一行定义
    public record UserDTO(Long id, String username, String email) {}
    
  • Pattern Matching 简化逻辑 : instanceof 和 switch 的模式匹配,让基于类型的条件分支处理变得直观且安全,减少了大量强制转型和模板代码。
  • Text Blocks 处理多行文本 :在编写 SQL、JSON、HTML 模板片段时,再也不需要丑陋的字符串拼接和转义了。

3.2 运行时性能与稳定性的增强

  • ZGC/Shenandoah GC :如果你的应用对延迟敏感(如金融交易、实时推荐),可以尝试启用 ZGC ( -XX:+UseZGC )。它旨在将 GC 停顿时间控制在 10 毫秒以下,且停顿时间不会随堆大小增长而增加。这为提升应用 SLA 提供了新的可能。
  • 容器感知优化 :高版本 JDK 对 Docker/Kubernetes 等容器环境的支持更好,能更准确地识别容器内存和 CPU 限制,避免因资源分配不当导致的 OOM 或性能浪费。

3.3 为未来技术栈铺路

  • GraalVM 原生镜像 :Spring Boot 3 对 GraalVM 原生镜像提供了前所未有的良好支持。将应用编译为原生可执行文件,能实现秒级启动、极低的内存占用。而这背后的 Spring AOT 引擎,其构建和优化过程严重依赖 JDK 17+ 的某些特性。坚守旧版 JDK,等于主动放弃了迈向云原生极致效率的这条路径。
  • Project Loom(预览) :虽然还在孵化中,但 Loom 带来的虚拟线程(轻量级线程)有望彻底改变 Java 高并发编程的范式。提前布局 JDK 17+,能让你在 Loom 正式发布时,以最小的迁移成本享受其红利。

4. 决策框架:不是所有项目都要立刻升级

当然,技术决策不能脱离业务上下文。并非所有项目都需要、或都应该立刻升级到 Spring Boot 3 + JDK 17。我们可以建立一个简单的决策框架来评估。

评估维度 建议立即升级 建议暂缓或分步升级 建议维持现状
项目阶段 全新项目、处于早期开发阶段 中型项目,处于活跃迭代期 遗留系统,处于维护期,很少改动
技术债 低,或愿意借此机会重构 中等,部分依赖已过时 高,依赖大量陈旧且无人维护的库
团队能力 团队熟悉新特性,有探索意愿 团队有学习能力,可分配专门资源 团队资源紧张,维护现有稳定态已是挑战
业务压力 业务允许一定的技术探索和试错 业务有稳定期可供升级 业务处于关键冲刺期,稳定性压倒一切
收益预期 能明确利用新特性提升开发效率或性能 为未来特性铺路,解决部分兼容性隐患 升级收益不明确,风险远大于收益

对于“建议暂缓”的项目,可以制定一个渐进式路线图:

  1. 第一步:依赖梳理 。在不改变 JDK 的情况下,先尝试将 Spring Boot 升级到 2.x 的最后一个版本(如 2.7.x),解决所有过渡期弃用警告。这能迫使你清理一部分技术债。
  2. 第二步:环境准备 。在开发、测试环境中搭建 JDK 17 的编译和运行环境,让团队开始熟悉。
  3. 第三步:新模块试点 。在新开发的微服务或独立模块中,直接使用 Spring Boot 3 + JDK 17,积累经验。
  4. 第四步:核心服务迁移 。选择某个业务压力较小、架构相对清晰的核心服务进行整体升级,跑通全流程。
  5. 第五步:全面推广 。基于试点经验,规划剩余服务的升级批次。

Spring Boot 将基线升至 JDK 17,看似是一个框架的“任性”要求,实则是整个 Java 生态在经历了多年缓慢演进后的一次集中发力。它划下了一条线,线的一边是兼容历史但步履蹒跚的过去,另一边是拥抱现代、追求效率与安全的未来。

对于开发者个人而言,这次升级是一个强烈的信号:停留在舒适区(JDK 8)所积累的技术负债,终有一天需要偿还。主动学习和适应 LTS 版本节奏,理解并应用现代 Java 特性,不再是一种“加分项”,而是保持职业竞争力的“必需品”。

对于团队和项目而言,升级决策需要权衡风险与收益。但无论如何,将“评估向 JDK 17 迁移的可行性”列入技术雷达,已经是一件值得马上开始做的事情。因为下一次 LTS——JDK 21——已经发布,更多的特性正在路上。技术的车轮从未停歇,最好的应对方式,不是抱怨,而是理解其方向,并驾驭它前行。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐