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 文件版本,通常是因为撞上了下面三种报错之一:

  • 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. 找到报错指向的文件:那个 .class,或者包含它的 JAR、WAR、EAR、AAR(Gradle 依赖在 ~/.gradle/caches/modules-2/files-2.1/ 下)。手里只有报错文字?把它贴到“粘贴报错”,点“解读”。
  2. 把文件拖到拖放区,或点“选择文件”。最大 1 GiB,嵌套的 jar 也会打开。
  3. 先看概要一行(需要 Java N 或更高版本),再在“按 Java 运行时版本检查”里选你的运行时,列出会被拒绝加载的类;多版本分层和 MANIFEST.MF 说明这个 jar 是怎么构建的。

真实输出示例

我们从 Maven Central 下载 Gson 2.11.0,拖进中文页面,复制了页面显示的内容。排版有压缩(标签和值放在一行,类、包和运行时列表有删节),所有数值原样保留。

样本: gson-2.11.0.jar(Apache-2.0),2026-11-02 从 Maven Central 下载,SHA-256 57928d6e…52a78b (repo1.maven.org/maven2/com/google/code/gson/gson/2.11.0/)

概要
需要 Java 7 或更高版本:META-INF/versions 之外最高的 class 文件版本是 51.0

文件类型                       JAR
文件大小                       291.4 KB
归档数(含嵌套 jar)           1
类                             224 个(基础层 223 个,META-INF/versions 中 1 个)
META-INF/versions 中的最高版本  Java 9(53.0)
使用预览特性的类               0
Kotlin 类(kotlin.Metadata)   0
模块(module-info.class)      com.google.gson

有 223 个类是最高版本 Java 7。
(上传的文件本身)  com/google/gson/ExclusionStrategy.class  Java 7(51.0)
(上传的文件本身)  com/google/gson/FieldAttributes.class    Java 7(51.0)
…

class 文件版本分布
class 文件版本  基础层类  META-INF/versions 中  预览
Java 9(53.0)  0         1                     0
Java 7(51.0)  223       0                     0

归档 (1)
(上传的文件本身)  224  Java 7(51.0)  Java 7 × 223  MULTI-RELEASE MODULE-INFO
  Created-By: Maven JAR Plugin 3.4.1
  Build-Jdk-Spec: 11
  Bundle-RequiredExecutionEnvironment: JavaSE-1.7, JavaSE-1.8
  Multi-Release: true

多版本分层
(上传的文件本身)  基础层                223  Java 7(51.0)
(上传的文件本身)  META-INF/versions/9/  1    Java 9(53.0)  JAVA 9 及以上使用

模块 (1)
(上传的文件本身)  META-INF/versions/9/module-info.class  com.google.gson  2.11.0  Java 9(53.0)
…

能做什么

输入:一个不超过 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 秒。

文件如何保持私密

文件不会被上传:

  • 检查器运行在一个单独的框架(iframe)里,来源是 tools.rea-reverse.com。下面说的处理都在这个框架里、在你的设备上完成。
  • 这个页面在正文之后展示一个广告。广告脚本运行在 rea-reverse.com 上,而框架属于另一个源(origin),浏览器不允许广告脚本或本页的任何其他脚本读取你拖入的文件、粘贴的报错或结果。
  • 框架的内容安全策略拦截它的脚本能发起的所有请求(connect-src 'none',也不加载任何第三方资源)。框架不能自行打开窗口或让本页跳转;只有你点击报告里的链接时才会打开页面。它传给本页的只有自身高度和下面这些统计事件。
  • 归档在你设备上的 Web Worker 里读取,粘贴的报错就在框架本身解析。
  • 类名、路径、模块名和清单字段值都按纯文本显示,文件里的任何内容都不会被当作 HTML 解析或执行。
  • 统计事件只有粗粒度信息:成功或错误码、文件类型(jar、war、aar 等)、类数量区间、大小区间和耗时区间;报错解读只记录有没有找到结果。绝不包含文件名、类名或包名、版本号、清单字段值或粘贴的文本。框架把事件交给本页,由本页发送。完整列表见隐私政策。
  • 不想要任何广告和统计?在单独页面打开检查器。

如何自己验证

  1. 打开浏览器开发者工具的 Network(网络) 面板,加载本页。发往 tools.rea-reverse.com 的请求就是框架:它的 HTML、样式、字体和脚本,以及 Worker 脚本 /_astro/java-worker-*.js。发往 Google 域名的请求是本页的广告。
  2. 清空日志,然后检查一个 jar,再粘贴一段报错。在过滤框里输入 domain:tools.rea-reverse.com:最多只会再出现一次 Worker 脚本 /_astro/java-worker-*.js(为下一次运行准备的新 Worker),没有任何请求带着你的数据。广告请求可能还会继续,但它们来自本页,不带框架里的任何内容。
  3. 点开来自 tools.rea-reverse.com 的 java-class-version-checker/ 文档,查看 Headers:它的 Content-Security-Policy 里有 connect-src 'none' 和 frame-ancestors https://rea-reverse.com。在 Chrome 里,Application(应用)> Frames 也能看到框架的源和策略。Worker 运行在 default-src 'none'; script-src 'self'; connect-src 'none' 这条策略下,浏览器不允许它发出任何网络请求。
  4. 检查隔离:在 Console(控制台) 里,上下文选 top,运行 document.getElementById('tool-frame').contentDocument,结果是 null;frames[0].document 会抛出 SecurityError。广告脚本拿到的也是这个结果。
  5. 或者先打开页面再断网(关掉 Wi-Fi,或在 Network 面板勾选 Offline),然后检查文件,照样能用。

局限

  • 只看版本号,不看 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”显示并附注说明,因为制作这张表时这些版本还没发布。

合规说明

请只检查你自己构建的 jar、开源库或你有权检查的文件,遵守相关软件的许可协议和你所在地的法律。本站与 Oracle、Google 和 OpenJDK 项目没有任何关联。另见免责声明。

对应的命令行

javap(JDK 17.0.18)一次只能看一个类的版本:

$ javap -v -cp gson-2.11.0.jar com.google.gson.Gson | grep -E "minor version|major version"
minor version: 0
major version: 51

版本号就在每个 class 文件的第 4 到 7 字节(先次版本号,后主版本号)。这里多版本分层里的模块描述符是 0x0035,即 53,对应 Java 9:

$ unzip -p gson-2.11.0.jar META-INF/versions/9/module-info.class | xxd -l 8
00000000: cafe babe 0000 0035                      .......5

MANIFEST.MF 里的构建信息。Build-Jdk-Spec: 11 是执行构建的 JDK,不是这些类需要的版本:

$ unzip -p gson-2.11.0.jar META-INF/MANIFEST.MF | grep -E "Created-By|Build-Jdk-Spec|Multi-Release|RequiredExecution"
Created-By: Maven JAR Plugin 3.4.1
Build-Jdk-Spec: 11
Bundle-RequiredExecutionEnvironment: JavaSE-1.7, JavaSE-1.8
Multi-Release: true

常见问题

“Unsupported class file major version 65” 是什么意思?

某个工具或 JVM 遇到了按 Java 21(主版本号 65)编译的类,读不了。Gradle、D8 和字节码库的版本比这个类老时就会这样报,要么换到更新的版本上运行,要么把类或依赖拖进来,找出是谁需要 Java 21。

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 版本。

怎样查看一个 JAR 是用哪个 Java 版本编译的?

把 JAR 拖进来:每个类都会被读取,概要给出 META-INF/versions 之外的最高版本,也就是你需要的 Java 版本。javap -v 显示的是同一个数字,但一次只能看一个类。

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/ 下的类替换它们。概要按基础层计算,分层里的最高版本单独显示,就像上面 Gson 里 Java 9 的 module-info.class。

更新于

相关教程