rea-reverse

Java class / JAR 版本检查

查一个 .class、JAR、WAR、EAR 或 Android AAR 到底要求哪个 Java 版本:逐个类读出 class 文件版本(含嵌套 jar),按 jar 和包给出最高版本,并列出多版本(Multi-Release)分层、module-info 和 MANIFEST.MF 里的构建 JDK。也可以直接粘贴 UnsupportedClassVersionError 报错,看懂里面的版本号。

文件不会离开你的浏览器. 归档在本地一个没有网络权限的 Web Worker 里读取,不会上传任何内容。 如何自行验证

把 .class、.jar、.war、.ear 或 .aar 文件拖到这里

或

也能读 Spring Boot fat jar(BOOT-INF/lib)、WAR 和 EAR、Android AAR(classes.jar 和 libs/)以及 ZIP64 归档。上限 1 GiB。

解读报错信息

支持 UnsupportedClassVersionError、"Unsupported class file major version"、javac 的 "wrong version" 和 "invalid target release"、Kotlin JVM target 以及 Android Gradle 插件的 Java 版本报错。文本只在本页处理。

class 文件主版本号与 Java 版本对照表
主版本号第 6–7 字节(十六进制)Java 版本长期支持
7100 47Java 27
7000 46Java 26
6900 45Java 25LTS
6800 44Java 24
6700 43Java 23
6600 42Java 22
6500 41Java 21LTS
6400 40Java 20
6300 3fJava 19
6200 3eJava 18
6100 3dJava 17LTS
6000 3cJava 16
5900 3bJava 15
5800 3aJava 14
5700 39Java 13
5600 38Java 12
5500 37Java 11LTS
5400 36Java 10
5300 35Java 9
5200 34Java 8LTS
5100 33Java 7
5000 32Java 6
4900 31Java 5
4800 30Java 1.4
4700 2fJava 1.3
4600 2eJava 1.2
4500 2dJDK 1.0.2 / 1.1

依据《Java 虚拟机规范》Java SE 27 版 §4.1(主版本号 45 到 71)。主版本号 45 同时对应 JDK 1.0.2 和 1.1。从 Java 12 起,次版本号 65535 表示这个类用到了该版本的预览特性。

本工具不做什么

  • 只读 class 文件版本,不分析代码:它看不出类调用了哪个 JDK API。用新 JDK 编译、只设了 -target 8 没用 --release 8 的类,放到 Java 8 上照样可能报 NoSuchMethodError。
  • 嵌套归档最多打开三层(ear 里的 war 里的 jar)。ZIP 数据前面拼了启动脚本的可执行 jar 读不了。

需要查 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 等)、类数量区间、大小区间和耗时区间;报错解读只记录有没有找到结果。绝不包含文件名、类名或包名、版本号、清单字段值或粘贴的文本。完整列表见隐私政策。
  • 工具页不展示广告。

如何自行验证

  1. 选文件之前先打开浏览器开发者工具,切到 Network(网络)面板。检查一个 jar,再粘贴一段报错。你只会看到页面资源和 worker 脚本 /_astro/java-worker-*.js 的请求,没有一个带着你的文件或文本。
  2. 或者加载页面后断网(关掉 Wi-Fi,或在 Network 面板勾选 Offline),再检查文件,照样能用。
  3. 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 项目没有任何关联。另见免责声明。

相关教程