本节目标:看懂 Java 的版本号与发布节奏,能在 JDK 21、25、27 之间做出有理由的选择,并知道怎么把「开发用 25、发布到 21」这件事在 Maven 里固定下来。
适用版本:JDK 25 LTS(Temurin 25.0.5);对照 JDK 21 LTS、JDK 27 特性版本。
为什么版本选择是工程问题
「装个 JDK 不就行了?」在个人项目里确实如此,但在团队项目里,JDK 版本会同时影响四件事:
- 能不能编译:源码用了哪个版本才有的语法。
- 能不能运行:产物的 class 文件版本,是否被目标环境的 JVM 接受。
- 能不能升级:依赖库、构建插件、容器镜像是否支持这个版本。
- 能不能获得修复:这个版本还在不在官方支持周期内。
这四件事的口径并不总是一致。开发机装最新版、CI 用另一版、生产再用第三版,是很多「本地能跑、线上报错」事故的来源。所以先把版本节奏搞清楚,再谈具体选哪个。
六个月一个特性版本,两年一个 LTS
OpenJDK 从 JDK 10 开始采用固定节奏:每年 3 月和 9 月各发一个特性版本,也就是每六个月一版。每六个版本(两年)挑一个作为 LTS(Long-Term Support,长期支持版)。
按这个节奏,2023 到 2027 年的版本线大致如下:
| 版本 | 发布时间 | 类型 | 说明 |
|---|---|---|---|
| JDK 21 | 2023-09 | LTS | 虚拟线程、分代 ZGC、switch 模式匹配正式版 |
| JDK 22 | 2024-03 | 特性版 | 只维护到下一个特性版发布 |
| JDK 23 | 2024-09 | 特性版 | 同上 |
| JDK 24 | 2025-03 | 特性版 | 同上 |
| JDK 25 | 2025-09 | LTS | 当前 LTS 主线 |
| JDK 26 | 2026-03 | 特性版 | 只适合评估 |
| JDK 27 | 2026-09 | 特性版 | 写作时的最新特性版 |
| JDK 28 | 2027-03 | 特性版 | 预计 |
| JDK 29 | 2027-09 | LTS | 下一个 LTS,预计 |
这张表可以直接用 Adoptium 的公开接口核对。写作时查询的结果是:
curl -s https://api.adoptium.net/v3/info/available_releases
{
"available_lts_releases": [8, 11, 17, 21, 25],
"available_releases": [8, 11, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27],
"most_recent_feature_release": 27,
"most_recent_feature_version": 28,
"most_recent_lts": 25,
"tip_version": 28
}
这组数字说明两件事:当前 LTS 是 25,最新特性版是 27,下一个已经在开发中的版本是 28。 本书选 25 作为主线,不是因为它最新,而是因为它是当前唯一同时满足「有长期修复」和「生态已经跟上」的版本。
特性版和 LTS 的区别不在功能多少,而在支持周期。特性版发布后,通常只在下一次发布前收到更新;LTS 会持续接收安全修复和关键缺陷修复,周期以年计。Oracle 的 LTS 提供五年 Premier 支持加若干年 Extended 支持,各发行版(Temurin、Zulu、Corretto 等)的具体承诺略有差异,但方向一致:生产环境跟 LTS,特性版用来评估新语法和新 GC。
版本号怎么读
java -version 在 JDK 25 上输出:
openjdk version "25.0.5" 2026-10-20 LTS
OpenJDK Runtime Environment Temurin-25.0.5+8 (build 25.0.5+8-LTS)
OpenJDK 64-Bit Server VM Temurin-25.0.5+8 (build 25.0.5+8-LTS, mixed mode, sharing)
25.0.5+8 按 $FEATURE.$INTERIM.$UPDATE.$PATCH 加构建号来读:
25:特性版本,决定语言特性和 class 文件版本。0:过渡版本,预留给少数需要中间版本的场景。5:更新版本,通常是安全修复和缺陷修复。8:构建号,同一版本可能因为打包原因有多个构建。
补丁升级(25.0.4 → 25.0.5)通常不需要改代码,但不能假设行为完全不变。GC 默认值、TLS 协议开关、字符集默认值都可能在补丁里调整。升级前看一眼 Release Notes,比出事后再回滚便宜。
class 文件版本和特性版本一一对应:
| 特性版本 | class 文件 major version |
|---|---|
| 8 | 52 |
| 11 | 55 |
| 17 | 61 |
| 21 | 65 |
| 25 | 69 |
这就是上一节 UnsupportedClassVersionError 里 69.0 和 52.0 的来源。它只认 major version,不认 25.0.5 这种字符串。
JDK 21、25、27 怎么选
把三个版本放在一起比较,选择就清楚了:
| 维度 | JDK 21 LTS | JDK 25 LTS | JDK 27 特性版 |
|---|---|---|---|
| 支持周期 | 长,仍在支持期 | 长,当前 LTS | 短,只到下一版 |
| 生态成熟度 | 很高 | 高,主流框架已支持 | 低,部分库尚未验证 |
| 适合场景 | 保守升级、存量系统 | 新项目默认选择 | 评估新特性、实验环境 |
| 语言特性 | 虚拟线程、记录模式、switch 模式匹配 | 再加 Scoped Values、模块导入声明、紧凑源文件、灵活构造器、紧凑对象头 | 以 25 为基础继续演进 |
| 风险 | 逐渐落后于新库要求 | 低 | 高 |
对本书的 library 项目,结论是:
- 开发和运行都用 JDK 25。 这样能用上 25 的正式特性,也方便卷四用 JFR、GC 日志做观测。
- 编译目标可以降到 21。 卷一使用的语法(record、sealed、pattern matching、text block)在 21 里都有,
--release 21可以产出能在 21 上运行的 class 文件。 - 不把 27 作为主线。 它适合另开分支做实验,不适合作为团队默认环境。
需要提醒的是,JDK 25 的正式特性里,真正会影响卷一代码的并不多。record、sealed、switch 模式匹配、文本块都是 21 就有的;25 新增的模块导入声明、紧凑源文件、灵活构造器,更多是让「小例子」和「构造器前置校验」写起来更顺。卷三会专门讲这些特性的边界,卷一先把 21 也能跑的地基打牢。
把版本固定下来
光在开发机装对 JDK 不够,还要让构建过程也认这个版本。三个做法按优先级排列。
用 --release 锁定编译目标
javac --release 21 -d out21 src/main/java/com/plume/library/LibraryApp.java
javap -verbose out21/com/plume/library/LibraryApp.class | grep 'major version'
major version: 65
不要用 -source 21 -target 21 代替 --release 21。后两者只改语法检查和版本号,仍然会链接当前 JDK 的 API,可能产出「版本号是 21、却调用了 25 才有的方法」的产物。--release 会同时切换 API 快照,是唯一可靠的做法。
在 Maven 里写死 release
<properties>
<maven.compiler.release>21</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
Maven Compiler Plugin 会把 maven.compiler.release 传给 javac --release。这样无论开发机装的是 25 还是 26,产物的目标版本都是 21。
用 Toolchains 固定构建 JDK
maven.compiler.release 只约束目标版本,不约束「用哪个 JDK 编译」。如果 CI 机器上默认 JDK 是 17,构建仍可能失败。Maven Toolchains 可以显式指定构建 JDK:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-toolchains-plugin</artifactId>
<version>3.2.0</version>
<executions>
<execution>
<goals><goal>toolchain</goal></goals>
</execution>
</executions>
<configuration>
<toolchains>
<jdk>
<version>25</version>
</jdk>
</toolchains>
</configuration>
</plugin>
配合 ~/.m2/toolchains.xml 里的 JDK 路径,构建就不再依赖 JAVA_HOME 的偶然值。团队项目里这一步很值:它把「我这台机器能编」变成「任何装了 25 的机器都能编」。
在 library 里落地
library 的版本策略写进 pom.xml 和 README,避免口头约定:
<properties>
<maven.compiler.release>21</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
开发机检查:
java -version
mvn -version
本机输出(节选):
openjdk version "25.0.5" 2026-10-20 LTS
OpenJDK Runtime Environment Temurin-25.0.5+8 (build 25.0.5+8-LTS)
Apache Maven 3.9.12
Java version: 25.0.5, vendor: Eclipse Adoptium, runtime: /Users/bingrong.yan/.jdks/jdk-25.0.5+8/Contents/Home
如果 mvn -version 里的 Java version 和 java -version 不一致,说明 Maven 用了另一个 JAVA_HOME。这是最常见的「命令行能跑、Maven 报错」原因,先对齐这两个值再排查别的。
依赖检查可以用 JDK 自带的 jdeps:
jdeps --version
jdeps --multi-release 21 -summary out
25.0.5
out -> java.base
jdeps 会告诉你产物依赖哪些模块。卷一只有 java.base,所以未来用 jlink 裁运行时镜像时,可以做到很小;卷二引入数据库驱动和 JSON 库后,这个列表会变长,届时要重新检查。
四个常见错误
「装了 JDK 25,但 javac 还是 17。」 PATH 里排在前面的 javac 来自另一个 JDK。用 which -a javac 和 javac -version 一起看,别只看 java -version。
「用 -source/-target 代替 --release。」 产物版本号对了,但可能链接了高版本 API,运行时报 NoSuchMethodError。统一用 --release。
「生产跟特性版,图新语法。」 特性版的支持周期只有半年,安全修复很快断供。除非你有明确的评估目标,否则生产跟 LTS。
「升级 JDK 只改 JAVA_HOME。」 还要检查 Maven/Gradle 插件、容器基础镜像、CI 缓存、字节码增强工具(如 Mockito、Byte Buddy)是否支持新版本。升级是一个链条,不是单点。
小结
- Java 每六个月发一个特性版,每两年一个 LTS;当前 LTS 是 25,最新特性版是 27。
- 版本号按
$FEATURE.$INTERIM.$UPDATE.$PATCH读,class 文件 major version 与特性版本一一对应(25 → 69)。 - 新项目默认选当前 LTS;存量系统跟已有 LTS;特性版只用于评估。
- 用
--release锁定目标版本,用maven.compiler.release固化到构建,用 Toolchains 固定构建 JDK。 library开发运行用 25,编译目标 21;卷一不使用 25 独有的语法。- 排查版本问题的第一步,是确认
java -version与mvn -version指向同一个 JDK。
版本和工具链定了,接下来要回答一个更基础的问题:我们天天说 JDK、JRE、JVM,这三者到底是什么关系?java 命令启动的又是什么?下一节拆开看。
阅读导航:上一节:1.1 Java 解决了什么问题 · 下一节:1.3 JVM、JRE、JDK 与字节码 。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。