Krakatau turns obfuscated classfiles into assembly you can rebuild
Java decompiler, assembler, and disassembler
At a glance
- What is it?
- Krakatau is a Rust toolchain for Java bytecode: a disassembler whose output can be edited as text and assembled back into a classfile, plus an assembler, and a decompiler that is not finished on the default branch.
- Who is it for?
- Pick Krakatau when your real task is editing, patching or byte-comparing Java bytecode, especially obfuscated code, and you can accept text assembly as your working format. Pass `--roundtrip` for anything carrying CLDC or Scala attributes.
- Can I use it commercially?
- Yes, with conditions. GPL-3.0 is a copyleft licence: if you distribute software that includes it, you must release that software's source code under the same licence. Running it internally without distributing it does not trigger that obligation.
- Is it still maintained?
- Yes. The repository last received commits 28 days ago.
- What is it written in?
- Mainly Rust, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Star history
- Storyyeller/Krakatau2,261 ★ · +0
| Repository | 2026-10-14 | 2026-10-23 |
|---|---|---|
| Storyyeller/Krakatau | 2.3k | 2.3k |
0 Stars in the last 9 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
Round-trippable assembly for obfuscated Java binaries
Krakatau targets a narrow job: moving Java bytecode into a text form a person can edit and turning that text back into a working classfile. The round trip matters more here than any decompiled output. A classfile that survived obfuscation is unreadable in a hex dump and hard to modify, yet it still has to assemble back into something a JVM accepts.
That defines the audience. People who reverse engineer or patch Java binaries, work with jar files whose symbol names were stripped, or want to diff two builds at the instruction level rather than the source level all want the same thing: text they control between two binary forms. The same loop suits people who would rather hand-write a classfile from scratch than generate one from Java source.
The code is Rust rather than Java, and the default branch is v2. Top-level entries list `Cargo.toml`, `src/`, `docs/` and `examples/`, plus a committed `Foo.class` at the root that nothing else in the listing explains.
Two disassembly modes and the byte-identity tradeoff
The disassembler has two modes, and picking one is a tradeoff between readable text and exact bytes. Default mode optimizes output for readability and ease of modification. Once reassembled, that output yields classfiles equivalent in behavior to the original from the perspective of the JVM specification, but not bit for bit identical, because constant pool entries may be reordered.
Roundtrip mode reverses the promise. Its output reassembles into a classfile that is bit for bit identical to the original, at the cost of keeping low level encoding information that makes the assembly files harder to read, including the exact order of constant pool entries.
Mode choice stops being cosmetic once attributes go beyond the standard set. The README recommends roundtrip mode for code relying on non-standard attributes, naming CLDC code and Scala code as the two cases, and `--roundtrip` or the short form -r on the command line selects it.
Output routing rules that differ between dis and asm
`--out` acts as a router in both subcommands, and the routing table is not the same on both sides. A directory value receives individual files. A value ending in `.jar` or `.zip` receives one zipfile at that location.
The suffix rules then diverge. For the disassembler, a .j suffix writes a single file, and one .j file can hold several classes written one after another. Leave `--out` out entirely and the disassembled classes go to standard output. For the assembler, a `.class` suffix writes the single output classfile, and a .j input carrying multiple classes makes the assembler error out in that case.
So the same bundle behaves differently under the two halves of the toolchain. The disassembler will fan a jar out into many readable files or collapse everything into one long .j file, while the assembler refuses to collapse a multi-class input into a single `.class`. Anyone scripting a pipeline has to decide the file layout before running either command.
Building krak2 and running a first round trip
Installation is a source build. Install Rust and Cargo first, then clone the repository and compile it; the result is a binary called `krak2` that you can call directly, add to PATH, or symlink.
cargo build --releaseStart with a single classfile in default mode. A `--out` value that is a directory receives one assembly file per class:
krak2 dis --out temp RecordTest.classFor a whole jar that must rebuild byte for byte, add `--roundtrip` (short form -r) and point `--out` at a zipfile:
krak2 dis --out disassembled.zip --roundtrip r0lling-challenge.jarGoing back to binary runs the assembler over a .j source, here one of the project's own test inputs:
krak2 asm --out temp Krakatau/tests/assembler/good/strictfp.jExpect individual assembly files under `temp` in the first case and one rebuilt classfile in the last. Input detection is by suffix: a filename ending in `.jar` or `.zip` is treated as an archive, and every `.class` inside it is disassembled, or every .j inside it is assembled.
The decompiler claim and the pointer to another branch
One contradiction sits inside this project's own text. The repository is introduced as a Java decompiler, assembler and disassembler, and the opening paragraph says Krakatau also provides a decompiler for converting Java binaries to readable source code. The Decompilation section then states that the v2 decompiler is still a work in progress and points you at Krakatau v1 on the master branch instead. With v2 as the default branch, a fresh clone contains no working decompiler.
Even the v1 route has a stated ceiling. The README says the decompiler does not support some Java 8+ features such as lambdas, and works best on older code, while the assembler half carries full support for the Java 19 bytecode specification plus some undocumented features found in old versions of the JVM.
Read the name as a family of tools rather than one deliverable. Two of the three pieces are usable from the default branch today, and the third sits on a branch you have to fetch on purpose.
Manifest details behind the krak2 command name
The manifest explains why the command is not named after the crate. The `[package]` block declares `name = "krakatau2"` at `version = "2.0.0-alpha"` with `edition = "2024"`, while the `[[bin]]` block names the executable `krak2` at `path = "src/main.rs"`, and the `[lib]` block builds the `krakatau2` library from `src/lib/mod.rs`. Anything you script against this tool has to use the shorter name.
Pinned dependency versions mix eras. The zip crate sits at 8.6.0, anyhow at 1.0.70, typed-arena at 2.0.1 and hexf-parse at 0.2.1, while clap is pinned back to 3.1.18 and lazy_static to 1.4.0, with regex at 1.4.3.
One manifest line has a visible consequence for input. The zip dependency is configured with `default-features = false` and `features=["deflate"]`, so deflate is the only archive method wired into the build. Syntax reference for the assembler lives in `docs/assembly_tutorial.md` and `docs/assembly_specification.md`.
Where javap and Jasmin fall short for this work
`javap` is the comparison the README draws, and the gap is specific rather than general: the Krakatau disassembler handles even highly obfuscated code, and its output can be reassembled into a classfile. A `javap` listing reads well for a normal class, but it produces no input you can hand back to an assembler, which is the edit step this workflow depends on.
`Jasmin` is the comparison on the assembler side. Krakatau's syntax is mostly a superset of Jasmin syntax with some minor incompatibilities, so Jasmin files are a starting point rather than drop-in input. In the other direction, Krakatau carries full support for the Java 19 bytecode specification and reaches some undocumented features found in old versions of the JVM, and its assembler also builds classfiles written by hand rather than disassembled from anything.
Checked against the specification in `docs/assembly_specification.md`, the migration cost from Jasmin is those incompatibilities, not missing instructions.
Where the upgrade cost sits in a text format pipeline
Two repository-level facts set expectations before any code runs. Licence `GPL-3.0` applies, so a modified Krakatau binary that you distribute carries source obligations, including the build setup used to produce it. And the project has no GitHub releases, with the last push recorded on 2026-09-25 on a non-archived repository, so the default branch is the only place changes surface.
Upgrade cost sits in the text format rather than in a dependency bump. Assembly files are the interface here, so the syntax reference in `docs/assembly_tutorial.md` and `docs/assembly_specification.md` is the contract to hold onto across any change to the assembler.
Four ready made inputs ship in the examples directory: `examples/hello.j`, `examples/greet1.j`, `examples/greet2.j` and `examples/minimal.j`. They give you something to assemble before you have disassembled anything of your own.
Editorial conclusion
Pick Krakatau when your real task is editing, patching or byte-comparing Java bytecode, especially obfuscated code, and you can accept text assembly as your working format. Pass `--roundtrip` for anything carrying CLDC or Scala attributes. Leave it if you need a working decompiler from the default branch, lambda support, or Jasmin input that assembles without edits. Verify first that the compiled binary reproduces your target classfile over one dis then asm pass, before trusting it with a jar you cannot rebuild.
Frequently asked questions
How do I build the Krakatau v2 binary?
Install Rust and Cargo, clone the repository, and run `cargo build --release`. The build produces `target/release/krak2`, which you can call directly, add to PATH, or symlink.
Does Krakatau v2 decompile Java bytecode back to source?
No. The README states that the v2 decompiler is still a work in progress and directs decompilation to Krakatau v1 on the master branch, while v2 is the default branch.
When should I use Krakatau roundtrip mode instead of the default?
Roundtrip mode reassembles into a classfile that is bit for bit identical to the original, at the cost of less readable output. The README recommends it for code relying on non-standard attributes, such as CLDC code or Scala code.
Can Krakatau take Jasmin syntax as input?
The syntax is mostly a superset of Jasmin syntax with some minor incompatibilities, so Jasmin files are a starting point rather than drop-in input. Krakatau adds full support for the Java 19 bytecode specification.
Can I reassemble Krakatau output instead of using javap?
That is the difference from `javap`, which the README names directly: Krakatau's disassembled output can be reassembled into a classfile, and its disassembler handles even highly obfuscated code.
What Java features does the Krakatau decompiler miss?
The README says the decompiler does not support some Java 8+ features such as lambdas, and that it works best on older code.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/storyyeller-krakatau)Star history chart for your README
[](https://hysenlabs.com/projects/storyyeller-krakatau)Was this useful?