需要查 class 文件版本,通常是因为撞上了下面三种报错之一:
java.lang.UnsupportedClassVersionError: com/example/App has been compiled by a more recent version of the Java Runtime (class file version 65.0), this version of the Java Runtime only recognizes class file versions up to 61.0- Gradle、Spring、Byte Buddy、JaCoCo,或 Android 构建里的 D8 / R8 报
Unsupported class file major version 69 - javac 报
class file has wrong version 61.0, should be 55.0
报错里的数字就是编译这个类的 Java 版本:主版本号 65 是 Java 21,61 是 Java 17,52 是 Java 8。报错没告诉你的,是哪个类、哪个依赖带着这个数字。这个页面会把你给的文件里每一个类都读一遍,直接列出来。
能做什么
输入:一个不超过 1 GiB 的文件:
- 单个
.class文件; - JAR,包括多版本(Multi-Release)jar、模块化 jar,以及条目超过 65,535 个的 shade / uber jar(ZIP64);
- Spring Boot 可执行 jar:
BOOT-INF/classes/里的应用类和BOOT-INF/lib/里的每个依赖; - WAR(
WEB-INF/classes/、WEB-INF/lib/),以及里面装着 WAR 和 jar 的 EAR; - Android AAR:里面的
classes.jar和libs/下的 jar。
嵌套归档最多打开三层,所以 EAR 里的 WAR 里的 jar 也能读到。文件类型按开头的字节判断,不看扩展名。
报告内容:
- 概要:文件需要的 Java 版本,即
META-INF/versions/之外最高的 class 文件版本;外加归档数、类数、预览类、Kotlin 类和模块。 - 按 Java 运行时版本检查:选 Java 8、11、17、21、25 或其他任意版本(最高到 Java 27),工具会列出该版本 JVM 会以
UnsupportedClassVersionError拒绝加载的所有类,以及它们所在的 jar。不选版本时,列出版本最高的那些类。 - class 文件版本分布:每个主版本号各有多少类,分基础层、
META-INF/versions/下和预览类三栏。 - 归档:每个 jar(上传的文件本身和每个嵌套 jar)一行:类数、最高版本、版本分布、标记(Multi-Release、module-info、Kotlin),以及
MANIFEST.MF里说明构建方式的字段:Build-Jdk、Build-Jdk-Spec、Created-By、Multi-Release、Automatic-Module-Name,有的话还有Implementation-Version、Main-Class、Start-Class。 - 多版本分层:多版本 jar 的基础层和每个
META-INF/versions/N/层,各自的类数和最高版本。如果某层的类需要比 N 更新的 Java,会标出来;清单里没有Multi-Release: true的 jar 里的分层类也会标出(JVM 会忽略它们)。 - 模块:每个
module-info.class,以及从Module属性读出的模块名和模块版本。 - 使用预览特性的类:次版本号为 65535 的类。
- 包:每个 jar 里每个包的最高版本,可筛选,每次显示 500 行。
- 导出:整份报告导出为 JSON,包含每个类的主、次版本号。
报错解读:把报错文本贴进来,工具会把里面的数字挑出来。支持 UnsupportedClassVersionError 的新旧两种措辞(旧的是 Java 8 及以前的 “Unsupported major.minor version 52.0”)、“Unsupported class file major version”、javac 的 “wrong version” 和 “invalid target release” / “release version N not supported”、Kotlin 的 “Cannot inline bytecode built with JVM target”,以及 Android Gradle 插件的 “requires Java 17 to run”。中文环境下的 javac 报错也能识别:类文件具有错误的版本 61.0, 应为 55.0、不支持发行版本 21、无效的目标发行版:17 和 无效的源发行版:17。每一条都会说明数字的含义和该改哪里。报错里写了运行时版本的,点一下就能拿上传的文件按那个版本检查。
怎么看这些数字
每个 class 文件的第 4 到 7 字节是次版本号和主版本号。从 Java 5 起,主版本号等于 Java 版本加 44:49 是 Java 5,50 是 Java 6,51 是 Java 7,52 是 Java 8,依此类推,71 是 Java 27。主版本号 45 对应 JDK 1.0.2 和 1.1,46、47、48 分别是 1.2、1.3、1.4。完整对照表在本页上方,依据的是《Java 虚拟机规范》Java SE 27 版 §4.1。
JVM 只能加载不高于自己主版本号的 class 文件。Java 17 最多认 61,所以版本为 65(Java 21)的类在它上面会报 UnsupportedClassVersionError。读字节码的工具,比如 Gradle 里的 ASM、Android 构建里的 D8,也有自己的上限:遇到比它代码里认识的版本更新的类,就报 “Unsupported class file major version”,哪怕你的 JDK 版本够新。所以同一个数字,在一条报错里意思是“运行时太老”,在另一条里却是“构建工具太老”。
从 Java 12 起,次版本号 65535 表示这个类用了预览特性。这种类只能在编译它的那个 Java 版本上、加 --enable-preview 才能加载。工具会把它们单独标出来。
工作原理
页面先读文件末尾的 ZIP 中央目录,再按文件顺序用 Blob.slice 成块读取条目数据,从这些块里解析本地文件头,然后用浏览器自带的 DecompressionStream 解压每个 .class 条目。一个 jar 里所有类解压后合计不超过 64 MiB 时,每个类都会完整解压,以便扫描 kotlin.Metadata 注解;超过这个量,每个类只解压前几百字节的压缩数据,足够拿到 8 字节的文件头。未压缩存放的嵌套 jar(Spring Boot 的 BOOT-INF/lib/ 就是这样存的)直接当作文件的一段切片来读;压缩存放的(在 AAR、WAR、EAR 里常见)会解压到内存中,每个最多 256 MiB。
ZIP 读取器和 APK 分析器用的是同一个,另外加上了 ZIP64 支持。文件里声明的每个数量和大小在使用前都有上限:每个归档 500,000 个条目,一份报告 1,000,000 个类,嵌套三层。损坏的、嵌套太深的、或解压后远大于压缩大小的嵌套归档会被跳过,并列在说明里。
.class 文件不会被加载、校验或运行。工具只看 8 字节的文件头、module-info.class 的 Module 属性,以及(为了识别 Kotlin)是否出现字符串 Lkotlin/Metadata;。
与 javap 对照过。测试样本用 JDK 11、17、25(Homebrew 的 OpenJDK 构建)编译,参数分别是 --release 8、11、17、21 和 25 --enable-preview,再打包成普通 jar、多版本 jar、模块化 jar、Spring Boot 结构的 jar、AAR、内含 WAR 的 EAR 以及 ZIP64 jar。这些样本里共 41 个 class 条目,工具报告的主、次版本号与 javap -v 的输出逐一相同。同样的 javap 对照也在 Gradle 缓存里的两个真实库上做过:androidx.core:core-ktx 1.13.1 的 AAR(130 个类,全部识别为 Kotlin)和 kotlin-stdlib 2.2.10(970 个类,其中包括 META-INF/versions/9/module-info.class),结果完全一致。
大文件:在 Apple M5 Pro 上,一个由 138 个库拼成的 164 MB Spring Boot 结构 jar(73,802 个类,全部完整读取,所以统计了 Kotlin),Chromium 用时 2.5 秒,WebKit 2.6 秒;一个 207 MB、含 120,811 个类的 shade 过的 ZIP64 jar(只读 class 文件头),Chromium 用时 3.4 秒,WebKit 2.2 秒。12 万个类的报告渲染最多还要再加 4 秒。
文件如何保持私密
文件不会被上传:
- 归档在你设备上的 Web Worker 里读取,粘贴的报错就在页面本身解析。
- 类名、路径、模块名和清单字段值都按纯文本显示,文件里的任何内容都不会被当作 HTML 解析或执行。
- 统计事件只有粗粒度信息:成功或错误码、文件类型(
jar、war、aar等)、类数量区间、大小区间和耗时区间;报错解读只记录有没有找到结果。绝不包含文件名、类名或包名、版本号、清单字段值或粘贴的文本。完整列表见隐私政策。 - 工具页不展示广告。
如何自行验证
- 选文件之前先打开浏览器开发者工具,切到 Network(网络)面板。检查一个 jar,再粘贴一段报错。你只会看到页面资源和 worker 脚本
/_astro/java-worker-*.js的请求,没有一个带着你的文件或文本。 - 或者加载页面后断网(关掉 Wi-Fi,或在 Network 面板勾选 Offline),再检查文件,照样能用。
- worker 运行在内容安全策略
default-src 'none'; script-src 'self'; connect-src 'none'之下,浏览器不允许它发出任何网络请求。
局限
- 只看版本号,不看 API。用新 JDK 编译、只设了
-target 8而没用--release 8的类,版本号是 52,却可能调用了只有新 JDK 才有的方法,放到 Java 8 上会报NoSuchMethodError,这一点本工具判断不了。 - 不处理 DEX。已经转成 DEX 的类(APK、
.dex文件)没有 class 文件版本。APK 请用 APK 分析器。 - 带启动脚本的可执行 jar(ZIP 数据前面拼了一段 shell 脚本,比如 Spring Boot 的 “fully executable” jar)读不了;嵌套超过三层的 jar、加密条目、STORED 和 DEFLATE 以外的压缩方式也不支持。
- Kotlin 识别靠在每个类里找
kotlin.Metadata注解字符串,所以只统计 Kotlin 编译出的类(以及引用了这个注解的类)。类合计超过 64 MiB 的 jar,只报告有没有META-INF/*.kotlin_module。 - 比 Java 27 更新的版本(主版本号 72 及以上)按“主版本号减 44”显示并附注说明,因为制作这张表时这些版本还没发布。
常见问题
class file version 61.0、65.0 分别是哪个 Java 版本?
61 是 Java 17,65 是 Java 21,69 是 Java 25(三个都是长期支持版本),55 是 Java 11,52 是 Java 8。Java 5 及以后,主版本号减 44 就是 Java 版本。
Gradle 报 “Unsupported class file major version 69”,可我的代码目标是 Java 17。
Gradle(或某个插件)读不了的,往往是 JDK 自己的类:Gradle 版本比你的 JDK 老,就跑不起来。让 Gradle 跑在它支持的 JDK 上、编译用 Java toolchain 指定版本,或者升级 Gradle。如果出问题的类在某个依赖里,把那个 jar 拖进来看看它的版本。
Android D8 报 “Unsupported class file major version”。
Android Gradle 插件里的 D8 会读所有依赖的 class 文件。把构建日志里点名的 AAR 或 JAR 拖进来检查:如果某个库是按比你的 AGP 里 D8 支持的更新的 Java 编译的,要么升级 AGP,要么换用这个库的旧版本。
我的多版本 jar 里有 Java 17 的类,工具却说只需要 Java 8?
多版本 jar 本来就是这样:Java 8 加载基础层的类,Java 17 及以上会用 META-INF/versions/17/ 下的类替换它们。概要按基础层计算,分层里的最高版本单独显示。
合规说明
请只检查你自己构建的 jar、开源库或你有权检查的文件,遵守相关软件的许可协议和你所在地的法律。本站与 Oracle、Google 和 OpenJDK 项目没有任何关联。另见免责声明。
相关教程
- 解决 Unsupported class file major version 和 UnsupportedClassVersionError:用 JDK 11、17、25、旧版 Gradle 和 D8 复现两类报错,再用
--release和 toolchain 修好。 - 用 REA、JADX 和 Apktool 分析 Android APK:从应用的 jar 和 AAR 一路看到反编译后的代码。
- APK / AAB 分析与上架体检:查看打包后应用的 Manifest、原生库和签名。
- .NET 元数据与 IL 查看器:给 .NET 程序集做同样的初步检查。