Published Build3 min read
A .class file is a byte-layout problem: cafebabe, big endian, and 12 pool entries
A dev.to walkthrough dumps javac's output with xxd and rebuilds it in Node.js. The lesson for anyone writing a code generator: the format is tight enough that bookkeeping beats theory.
Written for builders.See today for builders

What happened
- Part 1 of the dev.to series built a Brainfuck interpreter with a tokenizer, a parser and an intermediate representation.
- The post states it will open a .class file with xxd, explain each byte, and build a generator that assembles the structure from scratch in Node.js.
- Running javac Hello.java produces Hello.class, a binary file that is not text, JSON or XML but raw bytes in a very specific structure defined in the JVM specification.
- The first four bytes of every .class file are always CA FE BA BE, the magic number identifying the file as a JVM ClassFile; if those bytes are absent the JVM refuses the file immediately.
- The four bytes after the magic number are the format version: 0000 is minor version 0 and 0034 is major version 52 decimal, which corresponds to Java 8.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
A dev.to post, part two of a series whose first instalment built a Brainfuck interpreter with a tokenizer, parser and intermediate representation, opens a compiled `Hello.class` with `xxd` and then rebuilds the same structure from scratch in Node.js [1][2]. The framing is worth borrowing, because the file the JVM expects is raw bytes in a structure fixed by the JVM specification [3], which makes emission an accounting job rather than a compiler-theory job.
Start with the header. Every `.class` begins with the four bytes `CA FE BA BE`, and the JVM refuses the file outright if they are absent [4]. The next four are the version: `0000` minor, `0034` major, which is 52, which is Java 8 [5]. Two more bytes give the constant pool count [6]. Four plus two plus two plus two is ten, which is exactly why the first pool entry in the dump sits at offset `0x0a` [7].
Then byte order. The JVM stores multi-byte numbers big endian, most significant byte first, while the x86 or ARM chip running your generator is probably little endian [8]. In Node the conversion for a 16-bit value is a single return statement: `(num >> 8) & 0xFF` for the high byte, `num & 0xFF` for the low, with more shifts of the same shape for four-byte fields [9][10]. Get it backwards and nothing traps at the site of the mistake, because the swapped bytes are still a legal number: `0x7530`, 30000, reads as `0x3075`, 12405 [11]. The post's debugging heuristic is the practical takeaway. If a generated class produces strange constant pool errors, check that you are writing big endian [12].
The pool itself is where the bookkeeping gets fiddly. For this `Hello.class` the count field is `0x000d`, 13, but indices start at 1 and the count is always n plus 1, so there are 12 entries [13]. The source program is a class with one method whose body is `return` [14], so twelve entries is the floor, not the interesting case [15]. Each entry opens with a tag byte, and the entries point at each other: entry 1 is `0a 00 02 00 03`, a CONSTANT_Methodref with class index 2 and name-and-type index 3, resolving to the superclass constructor `Object.<init>()V` [16]; entry 2 is `07 00 04`, a CONSTANT_Class naming index 4 [17]; entry 4 is tag `01`, a CONSTANT_Utf8 of length `0x0010` holding the 16 bytes `java/lang/Object` [18]. Bytecode never stores a string inline. Everything is reached through the pool by index [19].
Types get their own encoding. `main` is not recorded as `void main(String[] args)` but as `([Ljava/lang/String;)V`, in the form (parameters)return, sitting in the pool as a Utf8 and referenced by index [20][21].
What to watch is the part the post defers. From major version 50, Java 6, the StackMapTable attribute is mandatory [22], so a generator targeting version 52 owes the verifier frames that no source-level construct asked for. That is the point where hand-rolled emission stops being transcription of a layout and starts requiring you to model the operand stack yourself. The post says that is part three [23]; the byte-layout half, on this evidence, is the cheap half.
Claim ledger
Ranked by verification strength, evidence, and original report placement.
- [1]
Part 1 of the dev.to series built a Brainfuck interpreter with a tokenizer, a parser and an intermediate representation.
- [2]
The post states it will open a .class file with xxd, explain each byte, and build a generator that assembles the structure from scratch in Node.js.
- [3]
Running javac Hello.java produces Hello.class, a binary file that is not text, JSON or XML but raw bytes in a very specific structure defined in the JVM specification.
ReportedView cited source - [4]
The first four bytes of every .class file are always CA FE BA BE, the magic number identifying the file as a JVM ClassFile; if those bytes are absent the JVM refuses the file immediately.
ReportedView cited source - [5]
The four bytes after the magic number are the format version: 0000 is minor version 0 and 0034 is major version 52 decimal, which corresponds to Java 8.
ReportedView cited source - [6]
The next two bytes after the version fields indicate the size of the constant pool; in the example they are 000d.
ReportedView cited source
Sources & coverage · 1 publisher
The reporting this story was synthesized from, earliest first. Every link goes to the original.
- dev.toEdy SilvaAug 12O que tem em um .class do Java?
Cited in this coverage: dev.to post 'O que tem em um .class do Java?'
Cited in this coverage: dev.to post

