Java Class File Inspector
A Java .class file is the JVM's own format rather than an archive: one class per file, big-endian from the first byte, and every name, type and constant written as an index into a single constant pool at the front. This inspector reads that structure in your browser and reports what javap -v reports, without a JDK installed and without the file leaving the machine. The header gives the class file version, and the major number maps onto a Java release: 45 is Java 1.1, 49 is Java 5, 52 is Java 8, and from 8 onwards every release adds exactly one, so 55 is Java 11, 61 is Java 17, 65 is Java 21 and 69 is Java 25. That number is what an UnsupportedClassVersionError is complaining about, and it names the oldest JVM that will load the file — never the JDK that compiled it, because javac --release 8 on a JDK 21 still emits a major 52 file. The access flags are decoded so you can see at a glance whether the file holds a class, an interface, an enum, an annotation or a record; a record has no access flag of its own and is identified by its Record attribute, while an enum is identified by ACC_ENUM. The constant pool is counted by tag, which is the fastest way to understand why a generated class is enormous: a few thousand Utf8 entries, or a wall of InvokeDynamic and MethodHandle entries from lambdas and string concatenation, show up immediately. Fields and methods are listed with their descriptors rendered as Java types, and every method with the max_stack, max_locals and code length taken from its Code attribute; abstract and native methods have no Code and show a dash. The referenced-class list is every CONSTANT_Class entry other than the class itself, which is the closest thing a single class file has to a dependency list. What it cannot tell you is just as useful: descriptors are erased, so a List<String> field reads as java.util.List unless the Signature attribute is present; a class compiled with -g:none carries no SourceFile, no line numbers and no local variable names, so its stack traces will be blank; and nothing here can see a class loaded reflectively by name at runtime.
How to use
- Drop a .class file onto the box, or click it to pick one. It has to be a single compiled class — a .jar is a zip, so unpack it first and choose the class you want.
- Read the overview row: the class file version, the Java release that version corresponds to, what kind of type it is, and how big the constant pool grew.
- Check the declaration block for the superclass, the interfaces, the class-level attributes and the SourceFile name. No SourceFile and no debug info means the class was built with -g:none.
- Scan the method table. Max stack, max locals and code bytes all come from each method’s Code attribute, so an abstract, native or interface method shows a dash instead.
- Open the constant pool breakdown when a class looks far larger than the code it contains; the tag counts usually name the culprit in one line.
Frequently asked questions
- What does "class file version 65.0" mean, and why do I get UnsupportedClassVersionError?
- The two numbers are the major and minor version from bytes 5 to 8 of the file. Major 45 was Java 1.1 and each of 46, 47 and 48 was 1.2, 1.3 and 1.4; from major 49 (Java 5) onwards the arithmetic is simply major minus 44, so 52 is Java 8, 55 is Java 11, 61 is Java 17, 65 is Java 21 and 69 is Java 25. UnsupportedClassVersionError means the running JVM is older than the major version in the file: the message quotes both numbers, and the fix is either a newer runtime or recompiling with --release set to the version you actually deploy on. A minor version of 65535 is special — it marks a file that uses preview features, and such a file only loads on exactly that release with --enable-preview.
- Why does the constant pool skip an index after a long or a double?
- Because the specification says so, and it is one of the classic ways a hand-written class reader goes wrong. A CONSTANT_Long or CONSTANT_Double entry takes two consecutive pool slots and the second one is unusable: nothing may point at it, and the next real entry starts one index later. The Java designers later called it a poor choice, but it is baked into every class file ever written. A reader that advances by one after a long silently misaligns the rest of the pool, so from that point on every field name, method name and class reference is read from the wrong entry — usually producing a plausible-looking but completely wrong listing rather than an error. This tool advances by two, which is why the pool count it shows is larger than the number of entries it lists.
- Are the strings inside a class file UTF-8?
- Almost, and the difference bites. CONSTANT_Utf8 uses modified UTF-8: a NUL character is encoded as the two bytes C0 80 instead of a single zero byte, so that no string ever contains an embedded zero and C code can treat names as null-terminated. A character outside the Basic Multilingual Plane is not written in the four-byte form real UTF-8 uses; it is split into its UTF-16 surrogate pair and each surrogate is written in the three-byte form, six bytes in total. Handing those bytes to a standard UTF-8 decoder gives a replacement character or an exception. This tool decodes each group to a UTF-16 code unit and lets the surrogate pair recombine, which is what the JVM does.
- Why does my List<String> field show as java.util.List?
- Because descriptors are erased. The field_info structure stores a descriptor, and a descriptor has no room for a type argument: Ljava/util/List; is all there is. Generic information is kept separately in an optional Signature attribute, which the compiler emits alongside the descriptor for generic classes, fields and methods, and which is used by reflection and by javap when it prints a declaration. That is why the attribute list here often includes Signature on a class or a member that looks otherwise ordinary. If you need to know a field is List<String> rather than a raw List, the Signature attribute is the only place in the class file that says so.
- How do I tell a class, an enum, a record and an annotation apart?
- From the access flags and one attribute. ACC_INTERFACE marks an interface, and an annotation type is an interface with ACC_ANNOTATION set as well, which is why javap prints an annotation as an interface extending java.lang.annotation.Annotation. ACC_ENUM marks an enum, whose superclass is java.lang.Enum and whose constants appear as static final fields also carrying ACC_ENUM, next to a synthetic $VALUES array. A record has no access flag at all: it is a final class extending java.lang.Record that carries a Record attribute listing the components, so the attribute is the only reliable test. ACC_MODULE marks a module-info file, which has no fields or methods and is not a class at all.
- Is the referenced-class list the same as this class’s dependencies?
- It is close, and its gaps are worth knowing. The list is every CONSTANT_Class entry in the pool, which covers the superclass, the interfaces, anything constructed, cast, caught or used as a field or method owner, and every array type mentioned in bytecode. It does not cover a type that appears only inside a descriptor: a method that takes a String and returns nothing puts (Ljava/lang/String;)V in the pool as a Utf8 entry, and java/lang/String need never appear as a CONSTANT_Class at all. It also cannot see a class named only in a string and loaded with Class.forName, or a service resolved at runtime. Treat it as the compile-time reference set, not as a complete runtime closure.
Related tools
Python .pyc Bytecode Inspector
Open a .pyc from __pycache__ in your browser: which CPython wrote it, timestamp or hash invalidation, the code object tree and the disassembly.
Gettext .mo Catalogue Inspector
Read a compiled GNU gettext .mo catalogue in your browser: byte order, header, every msgid and translation, contexts, plural forms and untranslated strings.
Semver Tools (Parser, Comparator, Bumper)
Parse, compare, and bump semantic versions side-by-side, with a range expander that shows what ^x.y.z and ~x.y.z actually cover.
Python Pickle Inspector
Disassemble a .pkl file in the browser: every opcode, the memo, the value it rebuilds, and whether loading it would run code.
ELF Binary Inspector
Open a Linux executable, .so or .o in your browser: architecture, shared libraries it needs, build ID, and whether it is PIE, NX and RELRO hardened.
Java .properties Parser
Parse a .properties file exactly as java.util.Properties.load does — separators, escapes, continuations — and see every line that will surprise you.