Open-source project
leibnitz27/cfr avatar
leibnitz27/cfr

CFR: a Java decompiler that runs on Java 6 and still reads the modern class file

This is the public repository for the CFR Java decompiler

2,734 stars335 forksJavaMIT

At a glance

What is it?
A standalone decompiler that handles modern language features while being compiled to an old bytecode target, plus the golden-file test harness that keeps its output stable.
Who is it for?
CFR's central bet is that a decompiler should not force a version decision on you. It reads class files from Java 9, 12 and 14, records included, while its own bytecode targets Java 6 so it starts on whatever JVM you happen to have, including the ancient one inside a container you are investigating.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 141 days ago.
What is it written in?
Mainly Java, 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.

From daily snapshots

Star history

  • leibnitz27/cfr2,734 ★ · +17
Star history
Repository2026-10-122026-10-23
leibnitz27/cfr2.7k2.7k

17 Stars in the last 11 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

Reading new class files from an old runtime

The unusual property of this decompiler is stated in one sentence in the README and everything else follows from it. CFR will decompile modern Java features, including much of Java 9, and switch expressions and instanceof patterns from 12 and 14, but the decompiler itself is written entirely in Java 6.

That sounds like a contradiction and is really the design. A decompiled class has to run on the JVM of whatever you are analysing, and that JVM is frequently not the one on your laptop. During incident response, artefact extraction or reverse engineering of a legacy application, being able to run `java -jar cfr.jar` against a Java 6 runtime without a second JDK is worth a great deal. The README also notes that CFR makes a decent attempt at turning class files from other JVM languages back into Java, which is broader than the name suggests.

The invocation is a single jar with either a class file path or a fully qualified class name on the classpath. To decompile a whole jar you pass the jar path instead, and add `--outputdir` because without it you get text on standard output rather than files.

code
java org.benf.cfr.reader.Main java.lang.Object

That is the smoke test the README gives after building, run from `target/classes`, and it decompiles a class from the JDK itself. `--help` lists the full argument set, and the options are extensive enough that you will want it open.

Where the binaries live, and why Maven Central lags

Three distribution channels are documented, and the differences between them are the kind of detail that saves an afternoon.

The main site at benf.org/other/cfr is where releases have always been published, with what the README calls a bunch of rambling musings from the author attached. Since version 0.145 the binaries also ship as GitHub release tags, which is where most people should look now. Maven Central is listed under `org.benf/cfr`, and the README explains the delay plainly: releases go there a few days late, deliberately, to allow for what it calls release regret. That is a decision to ship binaries you would rather withdraw than ship something you would rather fix, and it is a reasonable one for a decompiler where a bad build is discovered the moment it meets real bytecode.

Snapshot builds come from GitHub workflow artifacts on the master branch, which is the usual tradeoff of running against master: closer to the current state, no stability guarantee.

The version numbering has not moved in years. The newest release listed is 0.152, published 2021-12-11, preceded by 0.151 in February 2021 and 0.150 in May 2020. Those notes are the most detailed part of the project's public record, and they read like a researcher's changelog: handling lengthy static enum initialisers, a fix for a possible loop in the conditional condenser, optional resugaring of unobfuscated retrolambda, relinking external constants, a race condition when decompiling with multiple drivers in parallel, sealed class support, and record support adjusted for Java 15.

That gap between the last release and the current state of the repository is the main thing to be aware of. The repository shows activity with its last push on 2026-06-04, so the code is being worked on, but there is no tagged build newer than 0.152. Anyone who needs behaviour that landed after that release is building from master.

Building it, and the JDK version trap in the instructions

The README calls building CFR dead easy, and for the most part it is: install Maven, then run `mvn compile` in the project root. The main class is `org.benf.cfr.reader.Main`, so a compiled tree under `target/classes` is immediately runnable.

The complication is entirely a consequence of the Java 6 target. If you hit a `maven-compiler-plugin...: Compilation failure` error, the README's diagnosis is that `JAVA_HOME` points at a JDK too new to support `6` for the `source` and `target` options. Newer JDKs dropped Java 6 as a compilation target, so a modern default JDK fails in a way that reads like a broken build rather than a version mismatch.

The fix is to point `JAVA_HOME` at something that still compiles to Java 1.6. The README's guidance is JDK 11, and it adds that the `java` on your `PATH` may need to be newer than 1.6 independently, because Maven 3.3.1 and later require Java 1.7 to run. The recommendation is to use JDK 8, 9, 10 or 11 for both `PATH` and `JAVA_HOME`.

The tree reflects a normal Maven Java project: `pom.xml`, `src/`, a `src-templates/` directory, `contributing.md` and the test tree under `decompilation-test/`. There is also a `.gitmodules` file, which is the subject of the next section and a real gotcha for anyone who clones without reading carefully.

Golden-file tests where the expected output is explicitly not sacred

CFR's test suite is unusual and worth understanding before contributing. The decompilation tests run as part of the Maven build and verify that current output matches previously recorded output. That is a snapshot test, and the author is explicit about its status: the expected output is not the gold standard, it merely describes the currently expected output, and there is nothing wrong with adjusting it if the changes to the decompilation results are reasonable.

That reframes the whole thing. A failing decompilation test does not automatically mean the decompiler regressed; it means output changed, and the reviewer decides whether the change is an improvement. It also means a contributor is expected to update expected files as part of a normal change rather than treat them as fixtures that must bend to the code.

The layout splits the input data from the expectations. Class and JAR test inputs live in a separate Git repository referenced as a submodule, which is why the README insists on `git clone --recurse-submodules`; clone without it and the tests have nothing to run against. The expected output and per-case CFR configuration live in this repository under `decompilation-test/test-data-expected-output`, deliberately separated so you can change expectations without touching the test data.

For class files, expectations sit at the mirrored path under `test-data-expected-output`, named after the class file: `MyClass.expected.java` for the decompiled source, `MyClass.options` for optional configuration, and optional `.expected.summary` and `.expected.exceptions` files for what the CFR API reports. For JARs, the expectations live in a directory named after the jar, with Java files that include the package name. Multi-release JARs get version subdirectories named `java-<version>`, following the JEP 238 convention. The test class is `org.benf.cfr.test.DecompilationTest`, and the README notes it can be run straight from an IDE for better diff output than Maven shows.

Per-case options, and how a test case is actually tuned

The options file mechanism is what makes the golden-file approach usable. Each line specifies one CFR option as a key and a value separated by a space, with empty lines and lines beginning with `#` treated as comments.

code
# Enable identifier renaming
renameillegalidents true

Per-case options rather than global defaults matters because decompilation quality is case specific. A class with obfuscated identifiers wants renaming enabled, a class with lambdas may want resugaring toggled, and a class using records or sealed types may need different handling again. Putting those switches in per-case files means the default configuration stays clean and each test documents why it needs what it needs.

A few of the options show up in release notes and illustrate how fine the control is. There is an `--antiobf true` flag for basic integer division control flow obfuscation, a `--constobf` option that folds decompiled constant expressions, and the README's own framing for the latter is that CFR will not do it unless asked because it tries to be true to the bytecode. That attitude explains the project's output character: where another decompiler guesses at the original source, CFR prefers to show what the bytecode says, and offers a flag when you would rather have the guess.

When a test fails in CI, the README points at a workflow artifact called decompilation-test-failures-diff containing the unified diff, which saves you from reproducing the failure locally before you can even see what changed.

Rules for filing issues, and where the real documentation lives

The issues section is three sentences long and completely unambiguous: if you file an issue, do not include copyright materials, and the maintainer will delete the issue. For a decompiler this is not squeamishness, since the natural thing to attach to a decompilation bug is the class file that breaks it, and that file is usually the thing you do not have the right to redistribute. Expect to describe the input instead.

The issue tracker is large, at 151 open, which is unremarkable for a decompiler of this vintage and mostly reflects that new class file formats keep arriving and each needs its own small fix.

Actual documentation is split across three places, and the README is honest that they are not equivalent. The FAQ page on the main site covers usage questions. The argument list behind `--help` is the complete reference. The main site carries the author's longer notes, which is where the reasoning behind decompilation decisions tends to appear. The README's own tone about that site, calling the release notes rambling musings, is affectionate rather than dismissive, and it is accurate about the format.

For getting started, the shortest path is to download the release jar, point it at a jar you want to read with `--outputdir` set to somewhere writable, and compare the output against what you expected. Only reach for the build if you intend to change something, and if you do, clone with `--recurse-submodules` before you start.

Editorial conclusion

CFR's central bet is that a decompiler should not force a version decision on you. It reads class files from Java 9, 12 and 14, records included, while its own bytecode targets Java 6 so it starts on whatever JVM you happen to have, including the ancient one inside a container you are investigating. That combination is why it keeps working in places newer decompilers have quietly stopped, and it comes with no runtime dependencies to resolve. The tradeoff is that the source is old enough to constrain your build tooling: you need a JDK that still accepts `source 6`, which means JDK 11 or earlier, and a Maven version new enough for its own requirements. Read the build note before your first `mvn compile`, because the failure mode there is a confusing compiler error rather than a clear version message. The other thing to understand before reading the test suite is the author's stated position that the expected output is not the gold standard, merely a record of current behaviour, which makes adjusting it a normal part of a pull request rather than an admission of failure. If you only need to read a jar, take the release jar from GitHub and skip the build entirely. If you are changing decompilation behaviour, the harness and its per-case options files are where the real work starts.

Frequently asked questions

Which Java decompiler is best?

It depends on what you need to read. CFR is built for the case where the runtime you are analysing may be older than your own: it decompiles Java 9, 12 and 14 features while its own bytecode targets Java 6, so it runs anywhere.

How can I decompile a .jar file?

Run the CFR jar and pass it the path to the jar you want to read. Add --outputdir pointing at a writable directory, otherwise you get decompiled text on standard output rather than files.

How can I convert a .class file to a Java file?

Pass CFR either the path to the class file or the fully qualified class name on your classpath. The main class is org.benf.cfr.reader.Main, and --help lists the full set of arguments.

What is the best online jar file opener?

CFR is distributed as a standalone jar that runs on your own machine rather than through a web service, which matters if the jar you are inspecting is not something you want to upload anywhere. Binaries are on GitHub release tags and on the main site.

Official sources

  1. leibnitz27/cfr on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
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.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/leibnitz27-cfr.svg)](https://hysenlabs.com/projects/leibnitz27-cfr)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/leibnitz27-cfr.svg)](https://hysenlabs.com/projects/leibnitz27-cfr)

Was this useful?