DEBUG.WIKI
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国
雨云;给项目一台云服务器
RackNerd;$21.99/年 · 1GB KVM

Gradle 8.1.1 在 Java 21 上报 Unsupported class file major version 65

Gradle 运行 JVM 与 Wrapper 版本不兼容;区分 Java 21 工具链支持和运行支持,检查实际启动入口并选择配套版本。

项目还没进入 Java 源码编译,Gradle 就在 Groovy 脚本的 semantic analysis 阶段报 Unsupported class file major version 65。如果实际组合是 Gradle 8.1.1 和 Java 21,先处理运行 Gradle 的 JVM 兼容性,不要只改 sourceCompatibility。

Java 21 对应的类文件主版本号是 65。Gradle 从 8.5 起支持在 Java 21 上运行;8.4 支持通过工具链使用 Java 21 编译和测试,但不等于它可以运行在 Java 21 上。这两个版本边界针对不同职责。

本文针对构建进程本身使用不受该 Gradle 版本支持的 Java 21 的场景。同一句报错也可能来自旧插件或其他字节码读取组件,不能仅靠错误文本认定升级到某个版本就一定解决。

确认项目实际使用哪一组版本

在项目根目录,使用项目自带 Wrapper 查看版本。Unix 类环境示例:

./gradlew --version
java -version

Windows 可使用 gradlew.bat --version。核对 Gradle 版本和输出中的 JVM 信息;不同 Gradle 版本的输出字段可能不同,新版本可能分别列出 Launcher JVM 与 Daemon JVM。

java -version 只说明当前终端找到的 Java,不能代替构建进程版本。IDE 的 Gradle JVM、CI 的 JDK、JAVA_HOME、org.gradle.java.home,以及项目配置的 Daemon JVM 条件,都可能影响实际选择。当前 Gradle 支持的 Daemon JVM 条件还可以优先于前述环境或属性,旧版不一定具备这一配置机制。

再检查 gradle/wrapper/gradle-wrapper.properties 中的发行地址。机器上安装了较新的全局 gradle,不会自动改变项目 Wrapper。出现“终端正常、IDE 失败”时,比较两边的启动入口与 JVM,不要先删除所有缓存。

按项目约束选择运行 JVM 或升级 Wrapper

如果项目暂时必须保留 Gradle 8.1.1,先选用它兼容、且项目插件也支持的 JDK 运行 Gradle。例如 Java 17 位于 Gradle 8.1.1 的运行兼容范围内。在 Unix shell 中可以先限定单次启动,路径需替换为真实 JDK 安装目录:

JAVA_HOME=/path/to/jdk-17 ./gradlew --version

这个检查不永久改系统配置。如果仍显示另一 JVM,继续查项目或用户级 Gradle 属性及实际启动方式,而不是认为环境变量已经生效。运行 JVM 改成 17,也不意味着现有 Gradle 8.1.1 已经获得完整的 Java 21 工具链支持;项目确实需要 Java 21 时,仍要选择相应兼容的 Gradle。

如果要让 Gradle 本身运行在 Java 21 上,选择至少 8.5、并同时符合插件与项目要求的目标版本。8.5 是这里的支持边界,不是建议所有项目今天都固定到该历史版本。升级可能影响插件 API、构建脚本与默认行为,需要连同迁移说明一起评估。

当旧 Wrapper 已无法在当前 Java 下执行时,先用旧 Gradle 兼容的 JDK 启动,再按项目流程运行 Wrapper 升级任务。目标版本由项目确定,下面的版本值是占位参数:

JAVA_HOME=/path/to/compatible-jdk ./gradlew wrapper --gradle-version TARGET_VERSION

Wrapper 任务会修改项目文件,也可能下载发行包,不是只读诊断。执行前保留 Wrapper 脚本、JAR 与属性文件的版本控制差异;按目标版本说明检查需要更新的完整文件集及发行包校验,不只手改一处 URL 就认定迁移完成。

sourceCompatibility 不控制构建进程

sourceCompatibility、targetCompatibility 和 Java 工具链主要影响编译、测试等任务使用的语言、产物或工具,不能直接替换已经启动的 Gradle JVM。构建脚本还没解释完成就报错时,把源代码目标降到 17,可能仍然由 Java 21 启动旧 Gradle。

反过来,用受支持的 JVM 跑 Gradle之后,也要确认任务工具链与应用部署环境。修好脚本加载不代表编译产物已适合线上 JRE;不要为了绕过启动错误,悄悄降低项目语言要求。

若版本组合已受支持而仍有相同错误,保留完整失败堆栈,定位最先拒绝类文件的插件或组件及它读取的文件。旧插件内置的字节码解析库,与旧 Gradle 运行 JVM 不兼容是不同分支。此时继续核对插件兼容范围,不反复清理缓存或来回切换 8.5。

回到原失败入口验收

分别从原来的终端、IDE 和 CI 入口确认实际版本,再运行项目原有的构建与测试流程。需要看到脚本解释通过、任务按预期工具链执行,而不是只看到一次版本命令正常。

修改前保留项目 Wrapper、JVM 选择配置和 CI 设置。回退时恢复配套组合,避免只退 Wrapper 却继续让旧 Gradle 使用 Java 21。原缓存可以保留;只有出现独立的缓存损坏证据时,再按项目流程定向处理。