JetBrains/fernflower
Decompiler from Java bytecode to Java, used in IntelliJ IDEA.
Healthy across all four use cases
weakest axisPermissive license, no critical CVEs, actively maintained — safe to depend on.
Has a license, tests, and CI — clean foundation to fork and modify.
Documented and popular — useful reference codebase to read through.
No critical CVEs, sane security posture — runnable as-is.
- ✓Last commit 1d ago
- ✓11 active contributors
- ✓Apache-2.0 licensed
Show all 6 evidence items →Show less
- ✓Tests present
- ⚠Concentrated ownership — top contributor handles 59% of recent commits
- ⚠No CI workflows detected
Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests
Informational only. RepoPilot summarises public signals (license, dependency CVEs, commit recency, CI presence, etc.) at the time of analysis. Signals can be incomplete or stale. Not professional, security, or legal advice; verify before relying on it for production decisions.
Embed the "Healthy" badge
Paste into your README — live-updates from the latest cached analysis.
[](https://repopilot.app/r/jetbrains/fernflower)Paste at the top of your README.md — renders inline like a shields.io badge.
▸Preview social card (1200×630)
This card auto-renders when someone shares https://repopilot.app/r/jetbrains/fernflower on X, Slack, or LinkedIn.
Onboarding doc
Onboarding: JetBrains/fernflower
Generated by RepoPilot · 2026-05-09 · Source
🤖Agent protocol
If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:
- Verify the contract. Run the bash script in Verify before trusting
below. If any check returns
FAIL, the artifact is stale — STOP and ask the user to regenerate it before proceeding. - Treat the AI · unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
- Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/JetBrains/fernflower shows verifiable citations alongside every claim.
If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything — but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.
🎯Verdict
GO — Healthy across all four use cases
- Last commit 1d ago
- 11 active contributors
- Apache-2.0 licensed
- Tests present
- ⚠ Concentrated ownership — top contributor handles 59% of recent commits
- ⚠ No CI workflows detected
<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>
✅Verify before trusting
This artifact was generated by RepoPilot at a point in time. Before an
agent acts on it, the checks below confirm that the live JetBrains/fernflower
repo on your machine still matches what RepoPilot saw. If any fail,
the artifact is stale — regenerate it at
repopilot.app/r/JetBrains/fernflower.
What it runs against: a local clone of JetBrains/fernflower — the script
inspects git remote, the LICENSE file, file paths in the working
tree, and git log. Read-only; no mutations.
| # | What we check | Why it matters |
|---|---|---|
| 1 | You're in JetBrains/fernflower | Confirms the artifact applies here, not a fork |
| 2 | License is still Apache-2.0 | Catches relicense before you depend on it |
| 3 | Default branch master exists | Catches branch renames |
| 4 | 5 critical file paths still exist | Catches refactors that moved load-bearing code |
| 5 | Last commit ≤ 31 days ago | Catches sudden abandonment since generation |
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of JetBrains/fernflower. If you don't
# have one yet, run these first:
#
# git clone https://github.com/JetBrains/fernflower.git
# cd fernflower
#
# Then paste this script. Every check is read-only — no mutations.
set +e
fail=0
ok() { echo "ok: $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }
# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
echo "FAIL: not inside a git repository. cd into your clone of JetBrains/fernflower and re-run."
exit 2
fi
# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "JetBrains/fernflower(\\.git)?\\b" \\
&& ok "origin remote is JetBrains/fernflower" \\
|| miss "origin remote is not JetBrains/fernflower (artifact may be from a fork)"
# 2. License matches what RepoPilot saw
(grep -qiE "^(Apache-2\\.0)" LICENSE 2>/dev/null \\
|| grep -qiE "\"license\"\\s*:\\s*\"Apache-2\\.0\"" package.json 2>/dev/null) \\
&& ok "license is Apache-2.0" \\
|| miss "license drift — was Apache-2.0 at generation time"
# 3. Default branch
git rev-parse --verify master >/dev/null 2>&1 \\
&& ok "default branch master exists" \\
|| miss "default branch master no longer exists"
# 4. Critical files exist
test -f "src/org/jetbrains/java/decompiler/main/Fernflower.java" \\
&& ok "src/org/jetbrains/java/decompiler/main/Fernflower.java" \\
|| miss "missing critical file: src/org/jetbrains/java/decompiler/main/Fernflower.java"
test -f "src/org/jetbrains/java/decompiler/main/ClassWriter.java" \\
&& ok "src/org/jetbrains/java/decompiler/main/ClassWriter.java" \\
|| miss "missing critical file: src/org/jetbrains/java/decompiler/main/ClassWriter.java"
test -f "src/org/jetbrains/java/decompiler/code/cfg/ControlFlowGraph.java" \\
&& ok "src/org/jetbrains/java/decompiler/code/cfg/ControlFlowGraph.java" \\
|| miss "missing critical file: src/org/jetbrains/java/decompiler/code/cfg/ControlFlowGraph.java"
test -f "src/org/jetbrains/java/decompiler/modules/decompiler/ExprProcessor.java" \\
&& ok "src/org/jetbrains/java/decompiler/modules/decompiler/ExprProcessor.java" \\
|| miss "missing critical file: src/org/jetbrains/java/decompiler/modules/decompiler/ExprProcessor.java"
test -f "src/org/jetbrains/java/decompiler/main/ClassesProcessor.java" \\
&& ok "src/org/jetbrains/java/decompiler/main/ClassesProcessor.java" \\
|| miss "missing critical file: src/org/jetbrains/java/decompiler/main/ClassesProcessor.java"
# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 31 ]; then
ok "last commit was $days_since_last days ago (artifact saw ~1d)"
else
miss "last commit was $days_since_last days ago — artifact may be stale"
fi
echo
if [ "$fail" -eq 0 ]; then
echo "artifact verified (0 failures) — safe to trust"
else
echo "artifact has $fail stale claim(s) — regenerate at https://repopilot.app/r/JetBrains/fernflower"
exit 1
fi
Each check prints ok: or FAIL:. The script exits non-zero if
anything failed, so it composes cleanly into agent loops
(./verify.sh || regenerate-and-retry).
⚡TL;DR
Fernflower is JetBrains' production analytical decompiler that converts Java bytecode (.class, .jar, .zip files) back into readable Java source code. It powers the class file viewer in IntelliJ IDEA and can run standalone via command line with 20+ customizable options (hes, hdc, dgs, ren, etc.) to control output formatting, generic signature handling, enum decompilation, and identifier renaming. Monolithic engine organized by concern: src/org/jetbrains/java/decompiler/code/ handles bytecode instructions and control flow (Instruction.java, ControlFlowGraph.java, ExceptionHandler.java); src/org/jetbrains/java/decompiler/main/ orchestrates decompilation (ClassesProcessor.java, ClassWriter.java); collectors/ (BytecodeSourceMapper, ImportCollector, VarNamesCollector) manage metadata and naming. Gradle/Bazel dual build; command-line entry point is Fernflower.java.
👥Who it's for
IntelliJ IDEA developers who need to view/debug compiled Java libraries; reverse engineers and security researchers who analyze Java bytecode; IDE maintainers building decompilation features; teams maintaining legacy Java codebases that need source reconstruction from binaries.
🌱Maturity & risk
Production-ready and actively maintained by JetBrains. The codebase is substantial (2.4M lines of Java) with mature infrastructure (Gradle/Bazel build, .iml module files for IntelliJ integration). No direct GitHub star count visible, but it ships in IntelliJ Community Edition, indicating wide production use. Issues reported to JetBrains' YouTrack (subsystem 'Java. Decompiler. Engine'), not GitHub, suggesting enterprise-grade issue tracking.
Low risk for production use but tight coupling to IntelliJ internals: the codebase integrates deeply with IntelliJ's build system (BUILD.bazel, .iml files) and depends on JetBrains' compiler infrastructure. No external dependency list visible in repo snapshot, but bytecode analysis is complex—regressions in decompilation accuracy can be subtle. Last commit age and PR activity not provided in the data; check YouTrack for recent issues to gauge responsiveness.
Active areas of work
Unknown from repo data—commit history and recent PRs not provided. Check JetBrains YouTrack (Java. Decompiler. Engine subsystem) for active issues and planned work. The repo includes modern tooling (BUILD.bazel alongside build.gradle.kts), suggesting recent maintenance.
🚀Get running
git clone https://github.com/JetBrains/fernflower.git
cd fernflower
./gradlew build # Uses Gradle wrapper; Kotlin DSL build.gradle.kts
# Or with Bazel: bazel build //...
Daily commands:
Build JAR: ./gradlew build → outputs to build/. Run standalone: java -jar fernflower.jar [options] <source> <destination>. Example: java -jar fernflower.jar -hes=0 -hdc=0 mylib.jar output/. See README for full option list (rbr, rsy, din, dgs, ren, etc.).
🗺️Map of the codebase
src/org/jetbrains/java/decompiler/main/Fernflower.java— Main entry point and orchestrator for the decompilation process; all decompilation workflows begin here.src/org/jetbrains/java/decompiler/main/ClassWriter.java— Converts decompiled AST structures back into readable Java source code; critical for output generation.src/org/jetbrains/java/decompiler/code/cfg/ControlFlowGraph.java— Builds and analyzes control flow graphs from bytecode; foundation for all structural decomposition and loop/exception handling.src/org/jetbrains/java/decompiler/modules/decompiler/ExprProcessor.java— Transforms bytecode instructions into high-level expressions; core logic for bytecode-to-Java expression conversion.src/org/jetbrains/java/decompiler/main/ClassesProcessor.java— Manages class loading and orchestrates nested class, lambda, and method processing workflows.src/org/jetbrains/java/decompiler/main/DecompilerContext.java— Thread-safe context holder storing decompilation state, preferences, and shared resources across modules.src/org/jetbrains/java/decompiler/code/Instruction.java— Low-level bytecode instruction abstraction; fundamental data structure throughout the decompilation pipeline.
🛠️How to make changes
Add a new code structure optimization pass
- Create a new helper class in src/org/jetbrains/java/decompiler/modules/decompiler/ following the naming pattern *Helper.java (e.g., MyOptimizationHelper.java) (
src/org/jetbrains/java/decompiler/modules/decompiler/MyOptimizationHelper.java) - Implement static methods that accept a Statement object and return modified/optimized statements (
src/org/jetbrains/java/decompiler/modules/decompiler/MyOptimizationHelper.java) - Register the optimization pass in ExprProcessor.processMethod() or the appropriate sequential processing location (
src/org/jetbrains/java/decompiler/modules/decompiler/ExprProcessor.java) - Add corresponding tests in the test suite (follows src/ structure in test directory) (
test/org/jetbrains/java/decompiler/MyOptimizationTest.java)
Add custom variable naming strategy
- Implement IVariableNamingFactory interface to create custom naming logic (
src/org/jetbrains/java/decompiler/main/extern/IVariableNamingFactory.java) - Create a factory implementation (e.g., MyVariableNamingFactory.java) that returns IVariableNameProvider instances (
src/org/jetbrains/java/decompiler/main/MyVariableNamingFactory.java) - Register factory via DecompilerContext.setVariableNamingFactory() during Fernflower initialization (
src/org/jetbrains/java/decompiler/main/Fernflower.java) - Variable names will be looked up during code generation in ClassWriter (
src/org/jetbrains/java/decompiler/main/ClassWriter.java)
Add support for custom bytecode source provider
- Implement IBytecodeProvider interface to define how to load class bytecode (
src/org/jetbrains/java/decompiler/main/extern/IBytecodeProvider.java) - Create implementation (e.g., MyBytecodeProvider.java) supporting your custom storage/protocol (
src/org/jetbrains/java/decompiler/main/MyBytecodeProvider.java) - Register provider during BaseDecompiler initialization via constructor or factory method (
src/org/jetbrains/java/decompiler/main/decompiler/BaseDecompiler.java)
🪤Traps & gotchas
No Maven/Gradle dependency file visible: repo snapshot omits pom.xml or dependency sections in build.gradle.kts—clone and inspect build.gradle.kts for runtime/compile requirements. Thread-local DecompilerContext: state is stored in thread-local variables (DecompilerContext), not passed as arguments—multi-threaded decompilation requires careful context isolation. Bytecode version sensitivity: decompiler hardcodes assumptions about Java bytecode format (class file version, instruction set); changes to Java versions may require updates across multiple instruction handler classes. No test directory listed in top 60 files: tests likely exist (two .iml files reference tests), but actual test code not visible—search src/test/ or test/ after cloning. Bazel/Gradle duality: project supports both build systems; ensure consistency when adding dependencies or sources.
💡Concepts to learn
- Control Flow Graph (CFG) — Fernflower converts linear bytecode into a CFG (ControlFlowGraph.java) to reason about loops, conditionals, and exception ranges; understanding CFG construction is essential for fixing decompilation of complex control flow
- Bytecode Instruction Set — Java bytecode (JVM instruction set) is the input; CodeConstants.java and Instruction.java model ~200 opcodes (ALOAD, INVOKESPECIAL, etc.); contributors must understand bytecode semantics to handle new instructions
- Exception Table & Exception Handling — Java bytecode stores exception handlers in an exception table (ExceptionTable.java, ExceptionHandler.java); Fernflower must reconstruct try-catch-finally statements from this table; critical for correct decompilation of error handling
- Type Inference & Generic Signature Reconstruction — Bytecode erases generic types (List<String> → List); the 'dgs' option enables reconstruction by parsing generic signatures from class file attributes; understanding this is key for handling generics correctly
- Stack-Based Virtual Machine Semantics — JVM is stack-based (operands pushed/popped via ALOAD, ASTORE, etc.); Fernflower tracks stack state to infer variable types and lifetimes; InstructionSequence.java models this state
- Synthetic & Bridge Method Detection — Compilers generate synthetic methods (lambdas, inner classes, bridges for generics); the 'rsy' and 'rbr' options hide these; Fernflower must identify them (via ACC_SYNTHETIC flag) to produce clean source
- Data-Flow Analysis — Decompiler traces variable definitions and uses (InstructionImpact.java models instruction effects); data-flow analysis recovers variable declarations and scopes lost in bytecode
🔗Related repos
cfr-decompiler/cfr— Alternative modern Java decompiler focused on recent Java features (records, sealed classes); competes in the same decompilation space with different heuristicsleibnitz/procyon— Older analytical Java decompiler; shares similar bytecode analysis approach but less actively maintained than FernflowerJetBrains/intellij-community— Parent repository containing IntelliJ IDEA; Fernflower is bundled and integrated here for IDE class file viewingjasperReport/jasperreports— Uses Fernflower internally for decompiling generated bytecode in reporting toolsgradle/gradle— Build ecosystem companion; uses bytecode analysis similar to Fernflower for dependency analysis and task introspection
🪄PR ideas
To work on one of these in Claude Code or Cursor, paste:
Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.
Add integration tests for LambdaProcessor and NestedClassProcessor edge cases
The repo has complex bytecode-to-source transformation logic for lambdas and nested classes (src/org/jetbrains/java/decompiler/main/rels/LambdaProcessor.java and NestedClassProcessor.java), but there's no visible test directory structure. Adding comprehensive integration tests would catch regressions in Java 8+ language feature decompilation and improve contributor confidence when modifying control flow or AST generation logic.
- [ ] Create test directory structure mirroring src/ (e.g., test/org/jetbrains/java/decompiler/main/rels/)
- [ ] Add test cases for LambdaProcessor covering: method references, nested lambdas, capturing variables, synthetic bridge methods
- [ ] Add test cases for NestedClassProcessor covering: inner classes, static nested classes, anonymous classes with complex inheritance
- [ ] Each test should decompile sample .class files and assert the output matches expected Java source patterns
- [ ] Integrate tests into build.gradle.kts with a test task
Add comprehensive decompiler option documentation (IFernflowerPreferences.java)
The codebase has src/org/jetbrains/java/decompiler/main/extern/IFernflowerPreferences.java which defines decompiler options, but the README.md only shows basic command-line syntax. Users and contributors cannot easily discover what options are available (e.g., bytecode mapping, variable naming, assertion handling). Adding a structured options guide would significantly improve usability and reduce support burden.
- [ ] Review IFernflowerPreferences.java to extract all available preference keys and their purposes
- [ ] Create a new DECOMPILER_OPTIONS.md file documenting each option with: name, default value, valid values, and usage example
- [ ] Update README.md to link to DECOMPILER_OPTIONS.md and include 2-3 practical examples (e.g., 'Enable bytecode-to-source mapping', 'Disable assertions', 'Custom variable naming')
- [ ] For each option, reference the relevant processor class (e.g., AssertProcessor.java, InitializerProcessor.java) for contributor context
Refactor bytecode instruction handling: extract CommonInstruction patterns from code/ subpackage
The src/org/jetbrains/java/decompiler/code/ directory has multiple instruction classes (Instruction.java, JumpInstruction.java, SwitchInstruction.java) with likely duplicated offset tracking, validation, and visitor patterns. Extracting a shared base abstraction and instruction factory would reduce code duplication, improve testability of individual instruction types, and make the codebase easier for new contributors to navigate.
- [ ] Analyze Instruction.java, JumpInstruction.java, and SwitchInstruction.java to identify common fields (offset, opcode, arguments) and methods
- [ ] Create an AbstractInstruction base class with shared state and a generic visit() pattern
- [ ] Refactor JumpInstruction and SwitchInstruction to extend AbstractInstruction, removing duplicated logic
- [ ] Create an InstructionFactory class in code/ to centralize instruction creation logic currently spread across InstructionSequence.java
- [ ] Add unit tests for AbstractInstruction and InstructionFactory in test/org/jetbrains/java/decompiler/code/
- [ ] Update FullInstructionSequence.java and SimpleInstructionSequence.java to use the new factory
🌿Good first issues
- Add unit tests for ExceptionHandler.java and ExceptionTable.java—these critical exception-handling classes lack visible test coverage in the file list; write tests covering nested try-catch, finally blocks, and exception range merging logic.
- Document the option-parsing logic in Fernflower.java: there are 20+ command-line options (rbr, rsy, din, dgs, etc.) but no in-code documentation explaining what each does or their interactions; add JavaDoc and examples for each option.
- Add support for Java 21+ class file features (like virtual methods, sealed classes): CodeConstants.java and the instruction handlers may need updates for recent Java versions; check bytecode spec and add missing opcodes or attributes.
⭐Top contributors
Click to expand
Top contributors
- @mikhail-pyltsin — 59 commits
- @BartvHelvert — 9 commits
- @bartekpacia — 9 commits
- @k15tfu — 7 commits
- @develar — 6 commits
📝Recent commits
Click to expand
Recent commits
d77e7f8— BAZEL-3120: Regenerate build files (Damian Baczyński)7e9b849— [build] Update build files (IJI-3451) (k15tfu)8047ee0— [java] IDEA-171737 Failing on jdk.Profile+Annotation (mikhail-pyltsin)d426f6e— [java-decompiler] IDEA-387848 Fernflower: Fix decompiler generating dead code in specific cases (mikhail-pyltsin)5ec0854— refactor [java]: Add test data path (BartvHelvert)c03ea9f— docs [java-decompiler]: re-add the correct spelling of Fernflower to README (bartekpacia)8056104— [build] Update build files (IJI-3451) (k15tfu)770b592— [build] Update build files (IJI-3451) (k15tfu)40aca88— [build] Split modules w/ production and test source roots (IJI-3451) (k15tfu)cc2a0ab— MRI-3612 jps2bazel: generate iml export sections (shalupov)
🔒Security observations
Fernflower is a Java decompiler codebase with a generally solid security posture. The primary concern is the inclusion of gradle-wrapper.jar in version control, which presents a moderate supply chain risk. The codebase lacks visible security scanning configurations in the build system. As a developer tool that processes untrusted bytecode input, additional input validation documentation and fuzzing tests would strengthen security. No hardcoded credentials, SQL injection, XSS, or critical infrastructure misconfigurations were identified in the visible file structure.
- Medium · Gradle Wrapper JAR Included in Repository —
gradle/wrapper/gradle-wrapper.jar. The gradle-wrapper.jar file is committed to the repository. This can be a security risk if the wrapper is compromised, as it's executed automatically by gradlew scripts. It's generally recommended to exclude binary JAR files from version control and have them downloaded on first use. Fix: Remove gradle-wrapper.jar from version control and add it to .gitignore. Users will download it on first gradle build execution. - Low · No Security Configuration Visible —
build.gradle.kts / gradle.properties. The codebase appears to be a decompiler tool that processes Java bytecode. While this is a legitimate development tool used in IntelliJ IDEA, there are no apparent security scanning configurations (e.g., OWASP Dependency-Check, Snyk) visible in the build configuration. Fix: Consider adding dependency scanning and security checks to the build pipeline. Configure tools like OWASP Dependency-Check or Snyk to automatically scan for vulnerable dependencies during builds. - Low · Insufficient Input Validation Documentation —
src/org/jetbrains/java/decompiler/main/extern/IBytecodeProvider.java. The decompiler processes Java bytecode files and generates decompiled source code. While no obvious code injection vulnerabilities are apparent from the file listing, there's no visible documentation of input validation mechanisms for malformed or malicious bytecode. Fix: Implement comprehensive input validation for bytecode files. Add fuzzing tests with malformed bytecode to identify potential crashes or unexpected behavior. Document the bytecode validation strategy.
LLM-derived; treat as a starting point, not a security audit.
👉Where to read next
- Open issues — current backlog
- Recent PRs — what's actively shipping
- Source on GitHub
Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.