Three error messages send people looking for a class file version:
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.0Unsupported class file major version 69from Gradle, Spring, Byte Buddy, JaCoCo, or D8 and R8 in an Android buildclass file has wrong version 61.0, should be 55.0from javac
Each one names a number, and the number is the Java release that compiled the class: major version 65 is Java 21, 61 is Java 17, 52 is Java 8. What the message does not tell you is which class or dependency carries that number. This page reads every class in the file you give it and shows you.
What it does
Input. One file of up to 1 GiB:
- a single
.classfile; - a JAR, including multi-release and modular jars, and shaded or “uber” jars with more than 65,535 entries (ZIP64);
- a Spring Boot executable jar: the application classes in
BOOT-INF/classes/and every library inBOOT-INF/lib/; - a WAR (
WEB-INF/classes/,WEB-INF/lib/) or an EAR with WARs and jars inside it; - an Android AAR: its
classes.jarand the jars inlibs/.
Nested archives are opened up to three levels deep, so a jar inside a WAR inside an EAR is still read. The file type is detected from the first bytes, not from its name.
Report.
- Summary: the Java version the file needs, which is the highest class file version outside
META-INF/versions/, plus the number of archives and classes, preview classes, Kotlin classes and modules. - Check against a Java runtime: pick Java 8, 11, 17, 21, 25 or any other release up to Java 27, and the tool lists every class a JVM of that version would refuse with
UnsupportedClassVersionError, with the jar it sits in. Without a runtime it lists the classes that have the highest version. - Class file versions: how many classes have each major version, split into base classes, classes under
META-INF/versions/and preview classes. - Archives: one row per jar (the upload and each nested one) with its class count, highest version, version spread, flags (Multi-Release, module-info, Kotlin) and the
MANIFEST.MFfields that say how it was built:Build-Jdk,Build-Jdk-Spec,Created-By,Multi-Release,Automatic-Module-Name, plusImplementation-Version,Main-ClassandStart-Classwhen present. - Multi-release layers: for a multi-release JAR, the base layer and each
META-INF/versions/N/layer with its class count and highest version. A layer whose classes need a newer Java than N is flagged, and so are versioned classes in a jar whose manifest lacksMulti-Release: true(the JVM ignores them). - Modules: every
module-info.classwith the module name and version read from itsModuleattribute. - Classes using preview features: classes with minor version 65535.
- Packages: the highest version per package and jar, filterable, 500 rows at a time.
- Export: the whole report as JSON, every class with its major and minor version.
Error message explainer. Paste the error text and the tool picks out the numbers: UnsupportedClassVersionError in both its current and its Java 8 wording (“Unsupported major.minor version 52.0”), “Unsupported class file major version”, javac’s “wrong version” and “invalid target release” / “release version N not supported”, Kotlin’s “Cannot inline bytecode built with JVM target”, and the Android Gradle plugin’s “requires Java 17 to run”. javac’s Chinese-locale messages are recognised too: the translated wrong-version, release-not-supported and invalid target / source release errors. Each finding says what the numbers mean and what to change. When the message names the runtime’s version, one click checks the uploaded file against that runtime.
Reading the numbers
Bytes 4 to 7 of every class file are the minor and major version. The major version is the Java release plus 44 from Java 5 on: 49 is Java 5, 50 Java 6, 51 Java 7, 52 Java 8, and so on up to 71 for Java 27. Major 45 covers JDK 1.0.2 and 1.1; 46, 47 and 48 are 1.2, 1.3 and 1.4. The full table, from the Java Virtual Machine Specification (Java SE 27 edition, §4.1), is on this page above.
A JVM loads class files up to its own major version. Java 17 reads up to 61, so a class with 65 (Java 21) fails there with UnsupportedClassVersionError. Tools that read bytecode, such as ASM inside Gradle or D8 in an Android build, have their own ceiling: they fail with “Unsupported class file major version” on classes newer than the version their code knows, even when your JDK is new enough. That is why the same number can mean “your runtime is too old” in one message and “your build tool is too old” in another.
Since Java 12, minor version 65535 marks a class that uses preview features. Such a class loads only on exactly the Java release that compiled it, with --enable-preview. The tool tags those classes.
How it works
The page reads the ZIP central directory at the end of the file, then reads entry data in large Blob.slice blocks in file order. Local headers are parsed from those blocks, and each .class entry is inflated with the browser’s built-in DecompressionStream. When the classes of a jar add up to at most 64 MiB uncompressed, each class is inflated whole so the scan for the kotlin.Metadata annotation can run; above that, only the first few hundred compressed bytes of each class are inflated, enough for the 8-byte header. Nested jars stored without compression (as Spring Boot stores BOOT-INF/lib/) are read in place as a slice of the file; compressed ones (inside an AAR, WAR or EAR) are inflated into memory, up to 256 MiB each.
The ZIP reader is the one the APK analyzer uses, extended with ZIP64. Every count and size from the file is capped before it is used: 500,000 entries per archive, 1,000,000 classes in a report, three levels of nesting. A nested archive that is damaged, nested too deep or that inflates far beyond its compressed size is skipped and listed under Notes.
The .class files are not loaded, verified or run. The only bytes looked at are the 8-byte header, the Module attribute of module-info.class and, for Kotlin, the presence of the string Lkotlin/Metadata;.
Tested against javap. The test fixtures were compiled with JDK 11, 17 and 25 (Homebrew OpenJDK builds) using --release 8, 11, 17, 21 and 25 --enable-preview, then packed as a plain jar, a multi-release jar, a modular jar, a Spring Boot style jar, an AAR, an EAR containing a WAR, and a ZIP64 jar. For each of the 41 class entries in them, the major and minor version the tool reports equal what javap -v prints. The same check against javap passed on two real libraries from a Gradle cache: the androidx.core:core-ktx 1.13.1 AAR (130 classes, all flagged as Kotlin) and kotlin-stdlib 2.2.10 (970 classes, including META-INF/versions/9/module-info.class).
Large files. On an Apple M5 Pro, a 164 MB Spring Boot style jar built from 138 libraries (73,802 classes, all read whole, so Kotlin was counted) took 2.5 seconds in Chromium and 2.6 seconds in WebKit. A 207 MB shaded ZIP64 jar with 120,811 classes, where only the class headers are read, took 3.4 seconds in Chromium and 2.2 seconds in WebKit. Drawing the report adds up to four more seconds for 120,000 classes.
How your files stay private
Your file is not uploaded:
- The archive is read in a Web Worker on your device, and the pasted error message is parsed on the page itself.
- Class names, paths, module names and manifest values are shown as plain text; nothing from the file is interpreted as HTML or run.
- Analytics events are coarse: success or an error code, the file type (
jar,war,aarand so on), a class-count bucket, a size bucket and a duration bucket, and for the explainer only whether something was found. They never contain the file name, class or package names, versions, manifest values or the pasted text. The full list is on the privacy page. - The tool page shows no ads.
How to verify this yourself
- Open your browser’s developer tools and switch to the Network tab before you choose a file. Check a jar and paste an error. The only requests are page assets and the worker script
/_astro/java-worker-*.js; none carries your file or the text. - Or load the page, go offline (turn off Wi-Fi, or tick Offline in the Network tab) and check a file. It still works.
- The worker runs under the Content Security Policy
default-src 'none'; script-src 'self'; connect-src 'none', so the browser does not let it make any network request.
Limitations
- Versions, not APIs. A class compiled with
-target 8by a newer JDK, without--release 8, carries version 52 but may call methods that only exist in that newer JDK; it then fails on Java 8 withNoSuchMethodError, which this tool cannot predict. - No DEX. Classes already converted to DEX (an APK, a
.dexfile) have no class file version. For APKs use the APK analyzer. - Executable jars with a launch script (a shell script in front of the ZIP data, as in “fully executable” Spring Boot jars) are not read, nor are jars inside a jar more than three levels deep, encrypted entries, or compression methods other than STORED and DEFLATE.
- Kotlin detection looks for the
kotlin.Metadataannotation string in each class, so only Kotlin-compiled classes count (and any class that refers to that annotation). For jars with more than 64 MiB of classes, only the presence ofMETA-INF/*.kotlin_moduleis reported. - Versions newer than Java 27 (major 72 and up) are shown as major minus 44 with a note, since those releases did not exist when this table was made.
FAQ
What Java version is class file version 61.0? 65.0?
61 is Java 17, 65 is Java 21, 69 is Java 25 (all three are long-term support releases), 55 is Java 11 and 52 is Java 8. Subtract 44 from the major version for Java 5 and later.
Gradle fails with “Unsupported class file major version 69”, but my code targets Java 17.
The class Gradle (or a plugin) could not read is often one of the JDK’s own classes: Gradle versions older than your JDK cannot run on it. Run Gradle on a JDK it supports and set the Java toolchain for compiling, or upgrade Gradle. If the class is in a dependency, drop that jar here to see its version.
Android D8 says “Unsupported class file major version”.
D8 in your Android Gradle plugin reads the class files of every dependency. Check the AAR or JAR named in the build log here: a library compiled for a newer Java than your AGP’s D8 supports needs either a newer AGP or an older version of that library.
My multi-release JAR has Java 17 classes, but the tool says it needs Java 8.
That is how multi-release jars work: Java 8 loads the base classes, and Java 17 and newer load the ones under META-INF/versions/17/ in their place. The summary uses the base layer and shows the versioned maximum separately.
Compliance
Check jars you built, open-source libraries or files you are authorized to inspect. Respect the licenses of the software you look at and the laws that apply to you. This site is not affiliated with Oracle, Google or the OpenJDK project. See the disclaimer.
Related guides
- Fix ‘Unsupported class file major version’ in Java and Gradle: both errors reproduced with JDK 11, 17 and 25, old Gradle and D8, and fixed with
--releaseand toolchains. - Android APK Analysis with REA, JADX and Apktool: from the jars and AARs of an app to its decompiled code.
- APK & AAB analyzer: manifest, native libraries and signing of the built app.
- .NET metadata and IL viewer: the same kind of first look for .NET assemblies.