JDK 21升级总结

一、背景

将公司的 Java 技术栈从 JDK 11 升级到 JDK 21,不仅仅是一次常规的版本更新,更是一次对生产力、系统性能、安全性和未来技术竞争力的战略投资。JDK 11 作为上一个长期支持版(LTS),稳定可靠,但自其发布以来,Java 平台经历了十个版本的迭代,积累了大量革命性的新特性和底层优化。JDK 21 作为最新的 LTS 版本,是这些创新的集大成者。

升级的必要性

  • 支持终结 :Oracle 对 JDK 11 的免费公开更新(Public Updates)已于 2023 年 9 月结束。这意味着,如果不购买商业支持,你的生产环境将 无法获得最新的安全补丁和错误修复 ,这对于任何暴露在网络环境下的应用来说都是一个严重的安全隐患。

  • 框架和库的硬性要求 :主流的开源框架和库正在快速拥抱新的 JDK 版本。例如, Spring Framework 6 和 Spring Boot 3.x 已经将最低版本要求提升至 JDK 17。这意味着,如果你的团队想使用这些框架的最新功能、性能优化和安全修复,升级 JDK 是一个无法绕过的前提条件。

二、JDK21版本特性

虚拟线程

虚拟线程 (Virtual Threads - Project Loom) :这是 JDK 21 的 王牌特性**。它从根本上改变了 Java 的并发编程模型。

  • 之前 (JDK 11) :我们依赖昂贵的平台线程(与操作系统线程 1:1 对应),通过复杂的异步编程(如 CompletableFuture)和线程池来处理高并发,代码复杂且难以调试。

  • 现在 (JDK 21) :我们可以用极其轻量级的虚拟线程,以 同步、顺序的编码风格写出高并发程序 。一个 JVM 可以轻松创建数百万个虚拟线程,使 I/O 密集型应用的吞吐量得到 数量级的提升 ,同时 显著降低了代码的复杂性 。

新一代垃圾收集器 (GC)

  • ZGC 和 Shenandoah 的成熟 :这两款低延迟 GC 在 JDK 21 中已成为生产可用级别,能够将 GC 暂停时间控制在 亚毫秒级别 ,对于需要稳定低延迟的实时应用(如交易系统、实时推荐)至关重要。

  • G1 GC 的持续改进 :作为默认 GC,G1 在新版本中也获得了大量优化,吞吐量和延迟表现比 JDK 11 中更好。

  • 新版本引入了大量语法糖和新特性,旨在消除冗长的“样板代码”,让代码更简洁、更安全、更具表达力。

语法糖和新特性

新版本引入了大量语法糖和新特性,旨在消除冗长的“样板代码”,让代码更简洁、更安全、更具表达力。

  • Records (记录类 - JDK 16) :一句话定义不可变的数据载体类 (DTO/POJO),自动生成构造函数、equals()、hashCode()、toString() 和 getter。

JDK 11

public final class Point {  
  private final int x;
  private final int y;
  // + 构造函数, getters, equals, hashCode, toString... (约50行代码)
}

JDK 21

public record Point(int x, int y) { } // 1行代码搞定  
  • Switch 模式匹配 (Pattern Matching for Switch - JDK 21) :让 switch 语句变得前所未有的强大和安全,可以直接对对象的类型和属性进行判断,消除了繁琐的 if-else 和类型强转。

JDK 11

Object obj = ...;  
if (obj instanceof String) {  
  String s = (String) obj;
  System.out.println("String: " + s.toUpperCase());
} else if (obj instanceof Integer) {
  // ...
}

JDK 21

Object obj = ...;  
switch (obj) {  
  case String s -> System.out.println("String: " + s.toUpperCase());
  case Integer i -> System.out.println("Integer: " + i);
  default -> { }
}
  • 文本块 (Text Blocks - JDK 15) :优雅地编写多行字符串,告别丑陋的 + 拼接和 \n 转义,尤其适合编写 SQL、JSON、HTML 等。

JDK 11:

String json = "{\n" + " \"name\": \"John\",\n" + " \"age\": 30\n" + "}";  

JDK 21:

String json = """  
  {
    "name": "John",
    "age": 30
  }
  """;

其他重要特性

  • Record 模式 (Record Patterns, JDK 21):优雅地解构 Record 对象。

  • Sealed Classes (密封类, JDK 17):更精确地控制类的继承关系,构建更严谨的领域模型。

  • var 关键字的改进:让局部变量类型推断更强大。

  • 更友好的 NullPointerExceptions:NPE 异常信息会明确指出哪个变量是 null。

三、Spring Boot版本选择

SpringBoot和JDK版本兼容

Spring Boot VersionJDK Version来源
2.18 – 12https://docs.spring.io/spring-boot/docs/2.1.x/reference/html/getting-started-system-requirements.html
2.2 – 2.38 – 15https://docs.spring.io/spring-boot/docs/2.1.x/reference/html/getting-started-system-requirements.html
2.48 – 16https://docs.spring.io/spring-boot/docs/2.4.x/reference/html/getting-started.html#getting-started-system-requirements
2.58 – 18https://docs.spring.io/spring-boot/docs/2.5.x/reference/html/getting-started.html#getting-started.system-requirements
2.68 – 19https://docs.spring.io/spring-boot/docs/2.6.x/reference/html/getting-started.html#getting-started.system-requirements
2.78 – 21https://docs.spring.io/spring-boot/docs/2.7.x/reference/html/getting-started.html#getting-started.system-requirements
3.017 – 21https://docs.spring.io/spring-boot/docs/3.0.x/reference/html/getting-started.html#getting-started.system-requirements
3.117 – 21https://docs.spring.io/spring-boot/docs/3.1.x/reference/html/getting-started.html#getting-started.system-requirements
3.217 – 23https://docs.spring.io/spring-boot/docs/3.2.x/reference/html/getting-started.html#getting-started.system-requirements

虽然Spring Boot 3.x 版本带来了许多激动人心的新特性,但其颠覆性的 javax.到 jakarta.命名空间迁移为项目带来了不可忽视的巨大挑战。从 Spring Boot 2.x 升级到 3.x 不仅仅是一次常规的版本升级,而是一次 伤筋动骨的底层 API 迁移。

什么是命名空间变更?

由于 Java EE 规范的所有权从 Oracle 转移到了 Eclipse 基金会,其名称也变更为 Jakarta EE。这导致了所有相关 API 的 Java 包名从 javax.  强制变更为 jakarta. 。例如:

  • javax.servlet.http.HttpServletRequest -> jakarta.servlet.http.HttpServletRequest

  • javax.persistence.Entity -> jakarta.persistence.Entity

  • javax.validation.constraints.NotNull -> jakarta.validation.constraints.NotNull

迁移成本巨大?

1、全局代码修改:这不仅仅是简单的“查找和替换”。项目中所有涉及到 Servlet API、JPA、Bean Validation 等规范的 import 语句都需要修改。对于一个成熟的大型项目,这涉及成百上千个文件的改动。
2、整个依赖生态系统的颠覆:这是最棘手的问题。不仅仅是我们的代码,我们所依赖的所有第三方库(如数据库驱动、消息队列客户端、缓存工具、安全框架、自定义 Starters 等)都必须提供与 Jakarta EE 兼容的新版本。
3、传递性依赖冲突:即使我们升级了直接依赖,这些依赖的传递性依赖(它们依赖的库)可能仍然停留在 javax 命名空间,这将导致灾难性的类路径冲突(ClassNotFoundException,NoClassDefFoundError),解决这些冲突非常耗时且痛苦。
4、潜在的“深水区” Bug:某些库可能声称兼容 Jakarta EE,但在边缘场景下存在未被发现的 Bug。这种因底层 API 变更引入的问题通常难以定位和修复。

决策结论:

选择 Spring Boot 2.7.18 意味着我们可以 完全避免 这个高风险、高成本的迁移过程,将团队的宝贵时间和精力聚焦于业务功能的开发和交付上。

四、依赖库版本

依赖库版本可通过如下链接进行获取: https://docs.spring.io/spring-boot/docs/2.7.18/reference/htmlsingle/#appendix.dependency-versions
https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-parent/2.7.18

依赖版本
org.springframework5.3.31
spring-data-redis2.7.18
spring-data-mongodb3.4.18
commons-lang33.12.0
commons-collections3.2.2
commons-collections44.4
jstl1.2
guava32.1.3-jre
jackson-mapper-asl1.9.8
jackson-core2.16.1
hibernate-validator6.2.5.Final
javax.el3.0.0
javax.validation2.0.1.Final
javax.xml.bind2.3.1
suishen.com.baidu.disconf2.6.38-SNAPSHOT
org.reflections0.9.11
lombok1.18.30
org.jetbrains24.0.1
suishen-libs3.0.0-jdk21-SNAPSHOT
suishen-redis3.0.0-jdk21-SNAPSHOT
suishen-webx-parent3.0-jdk21-SNAPSHOT
suishen-webx-core3.0-jdk21-SNAPSHOT
suishen-root-pom3.0-jdk21-SNAPSHOT

五、升级步骤

开发工具准备:

  • idea升级到2025.2版本
  • maven使用3.6.1版本
  • tomcat 8或9
步骤1、pom文件修改

(1)升级suishen-webx-parent版本

    <parent>
        <groupId>suishen-webx</groupId>
        <artifactId>suishen-webx-parent</artifactId>
        <version>3.0-jdk21-SNAPSHOT</version>
    </parent>

(2)完成后确认项目中的其他依赖库版本:

步骤2、更改applicationContext-mongodb.xml配置

(1)原配置:spring-data-mongodb 1.10.15.RELEASE

!-- 定义mongo对象,对应的是mongodb官方jar包中的Mongo,replica-set设置集群副本的ip地址和端口,多个以英文逗号分割 -->
    <mongo:mongo-client id="mongoClient" replica-set="${mongo.hostport}" credentials="${mongo.username}:${mongo.password}@${mongo.dbname}">
        <mongo:client-options
                connections-per-host="${mongo.connectionsPerHost}"
                threads-allowed-to-block-for-connection-multiplier="${mongo.threadsAllowedToBlockForConnectionMultiplier}"
                connect-timeout="${mongo.connectTimeout}"
                max-wait-time="${mongo.maxWaitTime}"
                socket-keep-alive="${mongo.socketKeepAlive}"
                socket-timeout="${mongo.socketTimeout}"/>
    </mongo:mongo-client>

    <mongo:db-factory dbname="${mongo.dbname}" mongo-ref="mongoClient"/>

(2)更改后配置 :spring-data-mongodb 3.4.18

 <!-- 构建连接字符串,使用 SpEL 表达式对用户名和密码进行 URL 编码,避免特殊字符问题 -->
    <bean id="mongoConnectionString" class="com.mongodb.ConnectionString">
        <constructor-arg value="#{'mongodb://' + T(java.net.URLEncoder).encode('${mongo.username}', 'UTF-8') + ':' + T(java.net.URLEncoder).encode('${mongo.password}', 'UTF-8') + '@${mongo.hostport}/${mongo.dbname}?authSource=${mongo.dbname}&maxPoolSize=${mongo.connectionsPerHost:500}&connectTimeoutMS=${mongo.connectTimeout:5000}&socketTimeoutMS=${mongo.socketTimeout:10000}&serverSelectionTimeoutMS=${mongo.maxWaitTime:5000}'}"/>
    </bean>

    <!-- 创建 MongoClient -->
    <bean id="mongoClient" class="com.mongodb.client.MongoClients" factory-method="create">
        <constructor-arg ref="mongoConnectionString"/>
    </bean>

    <!-- 创建 MongoDatabaseFactory -->
    <bean id="mongoDbFactory" class="org.springframework.data.mongodb.core.SimpleMongoClientDatabaseFactory">
        <constructor-arg ref="mongoClient"/>
        <constructor-arg value="${mongo.dbname}"/>
    </bean>
步骤3、修改相关代码

mongo排序

修改前:new Sort(Sort.Direction.DESC, "startTime")

修改后:Sort.by(Sort.Direction.DESC, "startTime")

reids指令

修改前:ZParams zParams = new ZParams().aggregate(ZParams.Aggregate.SUM).weightsByDouble(weightsDouble)

修改后:ZParams zParams = new ZParams().aggregate(ZParams.Aggregate.SUM).weights(weightsDouble)
步骤4、eone流水线配置更改

基础环境:tomcat9_jdk21

步骤5、发布观察日志
  • 应用启动日志:检查是否有类加载错误、依赖冲突或配置问题
  • 性能指标:监控 CPU、内存使用情况,观察 GC 日志,确认 ZGC/G1 运行正常
  • 功能验证:全面回归测试核心业务功能,确保升级后功能正常
  • 错误日志:密切关注应用错误日志,特别关注与 JDK 版本相关的异常
  • 第三方服务调用:验证与外部服务(如数据库、Redis、消息队列等)的连接和交互是否正常

六、总结

本次 JDK 21 升级是一次重要的技术迭代,不仅解决了 JDK 11 安全支持终止的问题,更为团队带来了:

  • 长期技术支持:JDK 21 作为最新的 LTS 版本,将获得至少 8 年的安全更新支持
  • 性能提升:虚拟线程、新一代 GC 等特性将显著提升应用的并发处理能力和响应速度
  • 代码质量:Records、模式匹配等现代语法特性让代码更简洁、更安全、更易维护
  • 技术竞争力:为未来采用 Spring Boot 3.x 等新技术栈奠定基础

升级过程中虽然需要处理依赖版本调整和部分代码适配,但通过选择 Spring Boot 2.7.18,避免了 Jakarta EE 命名空间迁移的巨大成本,在获得 JDK 21 核心优势的同时,保持了升级路径的平稳可控。

建议在升级完成后,逐步探索和应用 JDK 21 的新特性(如虚拟线程),充分发挥新版本的技术优势,持续提升系统的性能和开发效率。

作者介绍:

  • 孙景亮 资深服务端开发工程师

微鲤技术团队

微鲤技术团队承担了中华万年历、Maybe、蘑菇语音、微鲤游戏高达3亿用户的产品研发工作,并构建了完备的大数据平台、基础研发框架、基础运维设施。践行数据驱动理念,相信技术改变世界。