Java 27 发布之后,Spring 项目该如何稳妥升级

2026-09-16 15 预计阅读时间: 1 分钟
来源: spring.io AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:10 分钟

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

如果 javajavac 来自不同目录,应先修复环境变量。否则可能出现“本地可以编译、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、压测和灰度发布逐步扩大范围,通常比单纯追逐版本号更可靠。


相关推荐