Spring Office Hours 这一期以 Java 27 发布派对为主题,并邀请 Billy Korando 参与讨论。与其只关注新语法和性能数字,开发团队更应该回答一个现实问题:现有 Spring 应用怎样验证、迁移并安全地运行在 Java 27 上?
由于来源摘要没有列出 Java 27 的具体特性,本文不猜测功能清单,而是给出一套可以直接用于真实项目的升级流程。
升级 JDK,不只是修改一个版本号
一次完整的 Java 升级至少涉及四层:
- 开发工具链:本地 JDK、Maven、Gradle、IDE 和代码生成工具能否识别 Java 27。
- 应用框架:当前 Spring Boot、Spring Framework 以及相关组件是否声明支持 Java 27。
- 第三方依赖:字节码增强、代理、序列化、测试覆盖率和监控 Agent 往往比普通业务库更容易出现兼容问题。
- 运行环境:容器基础镜像、Kubernetes 节点、CI Runner 和生产 JVM 参数是否同步更新。
因此,不要一上来就在主分支中把 java.version 改成 27。更稳妥的方式是先建立升级分支,同时保留当前生产版本作为对照基线。
对于 Spring 应用,尤其需要检查这些依赖:
- Spring Boot 与 Spring Framework 的官方兼容范围;
- Hibernate、Jackson、Netty、Tomcat、Jetty 或 Undertow;
- Lombok、MapStruct、Byte Buddy、ASM 等编译期或字节码工具;
- JaCoCo、Mockito、APM Agent 和 Java Agent;
- GraalVM Native Image 或 Spring AOT 流程,如果项目正在使用它们。
这些组件可能会读取 class 文件、生成代理或访问 JVM 内部实现。即使普通 Java 代码可以编译,它们也可能在启动、测试或运行期间失败。
先做一个最小化的 Java 27 冒烟测试
安装 Java 27 后,可以先运行一个不依赖 Spring 的小程序,确认命令行、编译器和运行时确实来自同一套 JDK。
下面的命令会创建、编译并运行一个最小项目:
mkdir -p java27-smoke/src/demo
cd java27-smoke
cat > src/demo/Main.java <<'EOF'
package demo;
public class Main {
public static void main(String[] args) {
Runtime.Version version = Runtime.version();
System.out.println("Runtime: " + version);
System.out.println("Vendor: " + System.getProperty("java.vendor"));
System.out.println("VM: " + System.getProperty("java.vm.name"));
if (version.feature() != 27) {
throw new IllegalStateException(
"Expected Java 27, but found Java " + version.feature()
);
}
System.out.println("Java 27 smoke test passed.");
}
}
EOF
java -version
javac --release 27 -d out src/demo/Main.java
java -cp out demo.Main
运行前需要把 JAVA_HOME 指向已安装的 Java 27。例如在 Linux 或 macOS 上可以这样检查:
export JAVA_HOME=/path/to/jdk-27
export PATH="$JAVA_HOME/bin:$PATH"
which java
which javac
java -version
javac -version
如果 java 和 javac 来自不同目录,应先修复环境变量。否则可能出现“本地可以编译、CI 无法运行”或 class 文件版本不匹配的问题。
把验证扩大到 Spring 应用
冒烟测试通过后,再让真实项目使用 Java 27。Maven 项目可以将编译目标明确设为 27:
<properties>
<java.version>27</java.version>
<maven.compiler.release>27</maven.compiler.release>
</properties>
Gradle Kotlin DSL 项目可以使用 Java Toolchain,避免构建过程意外选择机器上的其他 JDK:
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(27))
}
}
tasks.test {
useJUnitPlatform()
}
接下来不要只执行编译,至少应覆盖下面几步:
# Maven
./mvnw --version
./mvnw clean verify
# 或 Gradle
./gradlew --version
./gradlew clean test
对 Spring Boot 服务,还应该真正启动应用并访问健康检查或核心接口。例如:
./mvnw spring-boot:run &
APP_PID=$!
for i in $(seq 1 30); do
if curl --fail --silent http://localhost:8080/actuator/health; then
echo
echo "Application is healthy on Java 27"
kill "$APP_PID"
wait "$APP_PID" || true
exit 0
fi
sleep 2
done
kill "$APP_PID" || true
wait "$APP_PID" || true
echo "Application did not become healthy" >&2
exit 1
这个示例假设项目启用了 Spring Boot Actuator,并暴露了 /actuator/health。如果没有 Actuator,请把 URL 替换为项目自己的轻量级健康接口。
测试时应重点观察:
- 应用上下文能否完整启动;
- Hibernate 实体扫描和数据库迁移是否正常;
- JSON 序列化结果是否变化;
- 动态代理、反射调用和注解处理是否报错;
- 集成测试、Testcontainers 和消息中间件客户端是否工作;
- 启动时间、吞吐量、延迟和内存占用是否偏离现有基线。
用 JDK 自带工具寻找隐藏风险
依赖内部 JDK API 的代码,可能在旧版本中只是产生警告,在新版本中却直接失败。构建出 JAR 后,可以使用 jdeps 扫描:
jdeps --jdk-internals target/*.jar
还可以使用 Java 27 自带的 jdeprscan 查找已弃用 API:
jdeprscan --release 27 target/*.jar
如果应用打包为 Spring Boot 可执行 JAR,扫描结果可能包含大量第三方依赖信息。此时应先判断问题来自业务代码还是依赖库,再决定升级依赖、替换实现或暂时隔离风险。
若 Java 27 提供预览特性,并且团队决定试用,编译与运行阶段都必须显式开启:
javac --enable-preview --release 27 -d out src/demo/Main.java
java --enable-preview -cp out demo.Main
预览特性适合实验和反馈,不应在缺少退出方案时直接进入核心生产路径。后续 Java 版本可能调整语法、API 或行为,升级成本需要单独评估。
把 Java 27 固定进 CI,而不是依赖 Runner 默认值
当 CI 平台和所选 JDK 发行版提供 Java 27 后,可以在 GitHub Actions 中显式安装对应版本:
name: java-27-verify
on:
push:
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Java 27
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "27"
cache: maven
- name: Show toolchain
run: |
java -version
javac -version
./mvnw --version
- name: Build and test
run: ./mvnw --batch-mode clean verify
如果当前 CI 或 JDK 发行版尚未提供 Java 27,应把 distribution 和安装方式替换为团队实际采用的供应商方案,而不是下载来源不明的二进制文件。
成熟项目可以先采用双轨验证:生产仍使用现有 LTS 或已批准版本,Java 27 构建暂时设置为非阻塞任务。依赖链稳定后,再把 Java 27 检查提升为合并条件。
是否立即采用:一份落地检查表
Java 新版本值得尽早测试,但“尽早测试”不等于“立即全量上线”。在决定切换之前,可以逐项确认:
- [ ] Spring Boot、Spring Framework 和关键依赖明确支持 Java 27;
- [ ] 开发机、CI、容器镜像和生产环境使用相同的 JDK 发行版与补丁级别;
- [ ] 单元测试、集成测试和端到端测试全部通过;
- [ ] 已扫描内部 API 与弃用 API;
- [ ] APM、覆盖率、Mock 和字节码增强工具验证通过;
- [ ] 已完成性能基准和容量测试,而不是仅凭默认参数推断收益;
- [ ] 灰度发布期间能够快速回滚到原 JDK;
- [ ] 预览特性与正式特性被明确区分。
发布派对适合了解 Java 平台的方向,生产升级则需要可重复的工程流程。先固定工具链,再验证框架与依赖,随后通过 CI、压测和灰度发布逐步扩大范围,通常比单纯追逐版本号更可靠。