A high-performance, enterprise-grade Java bytecode hardening suite and desktop GUI written in modern C++20 and Tauri 2.0 (React 19 + TypeScript + Tailwind CSS). Engineered to protect compiled Java archives (.jar) from Java 8 through Java 25 against cutting-edge decompilers (Fernflower, CFR, Procyon, JD-GUI, Ghidra).
| Pass | Name | Description |
|---|---|---|
| 01 | Debug Stripping | Strips LineNumberTable, LocalVariableTable, LocalVariableTypeTable, SourceFile, SourceDebugExtension, and method parameter metadata. |
| 02 | Code Shrinking | Tree-shaking reachability analysis removes dead classes, methods, fields, and attributes. Respects keep rules. |
| 03 | Access Reduction | Auto-privatizes unreferenced public and protected methods and fields to private while respecting inheritance, reflection contracts, and keep rules. |
| 04 | Dynamic Reflection Proxy | Rewrites external invocations into cached MethodHandle.invokeExact dispatches (no boxing, no Method.invoke overhead), with shared lookup helpers in the global cache class. Sanitizes Constant Pool imports to eliminate decompiler references. |
| 05 | Multi-Type Number Obfuscation | Decomposes constants across int, long, float, and double into multi-step arithmetic splits, bitwise XOR/negation networks, and IEEE-754 bitcast transforms (Float.intBitsToFloat, Double.longBitsToDouble). |
| 06 | Identifier Renaming | Scrambles package, class, method, and field names into confusing homoglyphic token chains (IlIIllIlIll_...). Supports package flattening, enum constant obfuscation, method-order scrambling, resource remapping, and keep rules. |
| 07 | InvokeDynamic Indirection | Rewrites direct calls into invokedynamic call sites resolved by encrypted bootstrap methods. |
| 08 | ConstantDynamic | Replaces constants with CONSTANT_Dynamic resolution (Java 11+). |
| 09 | Control Flow Flattening | Obfuscates method control flow using opaque predicates (incl. mixed boolean-arithmetic identities), switch-dispatch flattening, and exception-dispatch handlers (decoy edges routed through try/catch with dead irreducible loops) to break decompiler control-flow graph analysis. |
| 10 | String Encryption | Encrypts string literals using rotating multi-cipher XOR algorithms with stack-split key construction and cached decryptors. Includes toggles for annotation string literal protection. |
| 11 | Decompiler Deterrence | Standards-compliant deterrents: return-type overloads, synthetic/bridge tags, phantom inner classes, custom attributes, method-order scrambling. |
| 12 | Class Encryption | Packs application classes into an encrypted in-memory bundle loaded by a synthesized Bootstrap ClassLoader. |
Supports standard classfile formats from Java 8 (v52) through Java 25 (v69):
- Java 8: Lambdas, default interface methods, streams.
- Java 11: Nestmates,
varin lambdas, String API enhancements. - Java 17: Records, sealed classes/interfaces, pattern matching for
instanceof, text blocks. - Java 21: Sequenced collections, virtual threads, record patterns, pattern matching
switch. - Java 25: Flexible constructor bodies, unnamed variables & patterns (
_).
obfuscator_cli [options] -i <input.jar> -o <output.jar># Obfuscate an input archive with default full protection
obfuscator_cli -i myapp.jar -o myapp-obfuscated.jar
# Keep a plugin API stable while obfuscating everything else
obfuscator_cli -i myapp.jar -o myapp-obfuscated.jar \
--keep 'interface com.myapp.Plugin { void onEnable(); void onDisable(); }' \
-m mapping.txt| Flag | Description |
|---|---|
-i, --input <file> |
Path to the source input JAR archive (Required). |
-o, --output <file> |
Path for the obfuscated output JAR archive. |
-c, --config <file> |
Path to a JSON configuration profile. |
-m, --mapping <file> |
Export a mapping.txt symbol map (for --retrace). |
--retrace <map> [trace] |
Deobfuscate a stack trace using a mapping file (reads stdin if no trace given). |
--seed <n> |
Fixed RNG seed for deterministic output (default: random per run). |
-h, --help |
Display CLI help menu and exit. |
-v, --verbose |
Enable verbose console logging. |
| Flag | Description | Default |
|---|---|---|
-p, --packages <pkgs> |
Comma-separated package wildcards to include (e.g. *, com.*, com.myapp.*). |
* |
-x, --exclude-packages <pkgs> |
Comma-separated package patterns to exclude from obfuscation. | None |
--exclude-classes <cls> |
Comma-separated class names to ignore. | None |
--keep <rule> |
ProGuard-style keep rule, repeatable (see Keep Rules). | None |
--keepclassmembers <rule> |
Keep matching members only; the class itself still shrinks/renames. | None |
--keep-file <file> |
File with keep rules (# comments, braces may span lines). |
None |
--ignore-dependencies |
Automatically ignore common shaded dependencies (org.apache.*, com.google.*, etc.). |
Off |
| Flag | Description | Default |
|---|---|---|
--rename-classes / --no-rename-classes |
Enable or disable class renaming. | Enabled |
--rename-packages / --no-rename-packages |
Scramble package hierarchy into confusing names. | Enabled |
--remove-packages |
Flatten and unnest matched classes directly to the root package. | Disabled |
--keep-main-class |
Preserve the entrypoint Main-Class name in the archive manifest. |
Disabled |
--rename-mode <mode> |
Identifier naming strategy: short, confusing, long, hex. |
long |
--obfuscate-enums / --no-obfuscate-enums |
Obfuscate enum constant names and rewrite static initializers. | Enabled |
--scramble-methods / --no-scramble-methods |
Permute method declaration order. | Enabled |
--remap-resources / --no-remap-resources |
Remap class references in plugin.yml and descriptors (--remap-plugin-yml is an alias). |
Enabled |
--resource-patterns <list> |
Comma-separated resource patterns to remap. | plugin.yml, services, … |
| Flag | Description | Default |
|---|---|---|
--import-packages <pkgs> |
Packages to proxy via dynamic reflection (e.g. *, com.*). |
* |
--import-java |
Enable reflection proxying for standard java.*, javax.*, and kotlin.* calls. |
Disabled |
--mh-lookup-min <n> / --mh-lookup-max <n> |
Shared MethodHandle lookup helpers in the cache class (range). |
2 / 4 |
--no-reflection |
Disable dynamic reflection proxying entirely. | Enabled |
| Flag | Description |
|---|---|
--no-strings |
Disable string literal encryption. |
--string-algo <algo> |
xor (fast rolling XOR) or multilayer (default). |
--split-keys / --no-split-keys |
Build each decryption key on the stack from split parts (default: on). |
--no-annotation-strings |
Leave annotation string literals in plaintext even when string encryption is active. |
--encrypt-annotation-strings |
Force encrypt annotation string literals (default). |
--strings-per-decryptor-min <n> |
Minimum strings bundled per generated decryptor method (default: 5). |
--strings-per-decryptor-max <n> |
Maximum strings bundled per generated decryptor method (default: 10). |
| Flag | Description |
|---|---|
--no-control-flow |
Disable control-flow flattening and opaque predicates. |
--flatten-control-flow / --no-flatten-control-flow |
Enable/disable switch-dispatch flattening. |
--flattening-intensity <n> |
Percentage of methods to flatten (0-100). |
--decompiler-traps / --no-decompiler-traps |
Irreducible-loop and overlapping-exception traps (default: on). |
--exception-dispatch / --no-exception-dispatch |
Route decoy edges through try/catch handlers with dead irreducible loops (default: on). |
| Flag | Description |
|---|---|
--shrink / --no-shrink |
Tree-shaking dead-code elimination (default: off). |
--shrink-classes / --no-shrink-classes |
Unused class removal. |
--shrink-methods / --no-shrink-methods |
Unused method removal. |
--shrink-fields / --no-shrink-fields |
Unused field removal. |
--shrink-attributes / --no-shrink-attributes |
Non-essential attribute stripping. |
| Flag | Description |
|---|---|
--encrypt-classes / --no-encrypt-classes |
Pack classes into an encrypted in-memory bundle (default: off). |
--launcher-class <name> |
Custom bootstrap loader class (default: Bootstrap). |
--encrypted-resource <path> |
Payload resource path (default: META-INF/.data/app.bin). |
--main-class <name> |
Override the application main class. |
| Flag | Description |
|---|---|
--indy-calls / --no-indy-calls |
invokedynamic call-site indirection with encrypted bootstrap methods (default: off). |
--condy / --no-condy |
ConstantDynamic call sites (Java 11+, default: off). |
--decompiler-deterrence / --no-decompiler-deterrence |
Standards-compliant deterrence suite (default: off). |
--overload-return-types / --no-overload-return-types |
Return-type-only overloads. |
--synthetic-bridge-tags |
ACC_SYNTHETIC / ACC_BRIDGE tagging. |
--phantom-inner-classes |
Phantom InnerClasses entries. |
--custom-attributes |
Custom specification-compliant attributes. |
| Flag | Description |
|---|---|
--no-strip |
Disable debug metadata stripping. |
--strip-kotlin-metadata / --no-strip-kotlin-metadata |
Drop kotlin.Metadata name tables (default: on). |
--no-rename |
Disable all identifier renaming (classes, methods, fields). |
--no-numbers |
Disable constant number splitting. |
--disable-access-reduction |
Disable access reduction pass (auto-privatization). Default: enabled. |
--reduce-access |
Force enable access reduction pass. |
Instead of passing command-line flags, you can supply a -c config.json profile:
{
"input_jar": "input.jar",
"output_jar": "output.jar",
"mapping_file": "mapping.txt",
"seed": null,
"target_packages": ["*"],
"exclude_packages": ["org.slf4j.*"],
"exclude_classes": [],
"keep_rules": ["-keep class com.myapp.Plugin { *; }"],
"ignore_dependencies": true,
"strip": {
"strip_line_numbers": true,
"strip_local_variables": true,
"strip_source_file": true,
"strip_source_debug": true,
"strip_signatures": false,
"strip_parameters": true,
"strip_kotlin_metadata": true
},
"shrink": {
"enabled": false,
"shrink_classes": true,
"shrink_methods": true,
"shrink_fields": true,
"remove_unused_attributes": true
},
"rename": {
"enabled": true,
"rename_classes": true,
"rename_methods": true,
"rename_fields": true,
"rename_packages": true,
"remove_packages": false,
"obfuscate_enums": true,
"keep_main_class": false,
"scramble_methods": true,
"remap_resources": true,
"resource_patterns": ["plugin.yml", "META-INF/services/*"],
"mode": "long",
"exclusions": []
},
"string_encryption": {
"enabled": true,
"algorithm": "MultiLayerCrypto",
"key_seed": 0,
"encrypt_indy_concat": true,
"randomize_decryptor_names": true,
"strings_per_decryptor_min": 5,
"strings_per_decryptor_max": 10,
"encrypt_annotation_strings": true,
"split_keys": true,
"exclusions": []
},
"reflection_proxy": {
"enabled": true,
"proxy_invocations": true,
"import_packages": ["*"],
"proxy_java_imports": false,
"mh_lookup_helpers_min": 2,
"mh_lookup_helpers_max": 4
},
"control_flow": {
"enabled": true,
"insert_opaque_predicates": true,
"flatten_blocks": true,
"flattening_intensity": 70,
"decompiler_traps": true,
"exception_dispatch": true
},
"number_obfuscation": {
"enabled": true,
"intensity": 70
},
"indy_call_sites": {
"enabled": false,
"target_packages": ["*"]
},
"condy": {
"enabled": false,
"encrypt_constants": true
},
"decompiler_deterrence": {
"enabled": false,
"overload_return_types": true,
"synthetic_bridge_tags": true,
"phantom_inner_classes": true,
"custom_attributes": true,
"scramble_method_order": true
},
"class_encryption": {
"enabled": false,
"launcher_class": "Bootstrap",
"resource_name": "META-INF/.data/app.bin",
"main_class": "",
"memory_scrubbing": true
},
"access_reducer": {
"enabled": true
}
}Key names mirror the CLI defaults ("mode" also accepts 0-4; "algorithm" accepts "xor"; "seed" accepts a number). The GUI writes this exact format via -c, so anything set in the app can be versioned as a file.
When invoked with --json, the CLI suppresses standard text logs and streams structured NDJSON (Newline Delimited JSON) events to stdout. This allows GUI wrappers (e.g. Tauri, Electron) to parse progress and telemetry in real time:
obfuscator_cli -i app.jar -o app_obf.jar --json-
Pass Start Event:
{"type":"pass_start","pass":"StringEncrypt","description":"Encrypts string literals"} -
Progress Event:
{"type":"progress","phase":"Obfuscating classes","percent":45.0,"processed":12,"total":28} -
Pass Complete Event:
{"type":"pass_complete","pass":"StringEncrypt","duration_ms":14} -
Execution Summary Event:
{ "type": "summary", "classes_processed": 28, "classes_renamed": 28, "methods_renamed": 89, "fields_renamed": 38, "strings_encrypted": 260, "methods_flattened": 69, "methods_exception_dispatched": 41, "reflection_proxies_created": 73, "numbers_obfuscated": 345, "members_privatized": 0, "debug_attributes_stripped": 214, "total_time_ms": 104 }
When an application bundles third-party libraries (e.g. net.blah shaded into myapp.jar), you often want to hide your application's interactions with that library without altering or corrupting the shaded classes themselves:
- Target Application Scoping: Specify
-p "com.testapp.*"(or--packages). Only classes incom.testappwill be scrambled, access-reduced, and control-flow flattened. Shaded classes innet.blah.*remain unaltered. - Dynamic Reflection Proxying: Specify
--import-packages "net.blah.*". - Sanitization & Erasure: Any invocation from
com.testapp.*tonet.blah.Testis rewritten into a synthesized proxy dispatching through a cachedMethodHandle.invokeExact(resolved once via shared lookup helpers), while allCONSTANT_Class_inforeferences tonet/blah/Testinsidecom.testapp's Constant Pool are replaced withjava/lang/Object. - Result: Modern decompilers (CFR, Fernflower, Procyon) will no longer detect that
com.testappimports or usesnet.blah, whilenet.blahremains fully operational inside the shaded archive.
ProGuard-style rules exempt matching classes/members from shrinking and renaming (other passes still apply). Essential for plugin APIs, serialization models, and anything reached reflectively.
obfuscator_cli -i app.jar -o app_obf.jar \
--keep 'interface com.myapp.Plugin { void onEnable(); void onDisable(); }' \
--keepclassmembers 'class com.myapp.Api { public *; }' \
--keep-file keeps.pro# keeps.pro — '#' comments, braces may span lines
-keep class com.myapp.PluginImpl { *; }
-keep class com.myapp.config.* { <fields>; }
-keepclassmembers class com.myapp.Api { java.lang.String get*(java.lang.String); }| Element | Forms |
|---|---|
| Directives | -keep (class + members are entry points), -keepclassmembers (members kept only if the class survives; the class still shrinks/renames). The directive may be omitted after --keep / in GUI input (defaults to -keep). |
| Class patterns | Exact (com.foo.Bar), pkg.* (direct package), pkg.** (subtree), * (all), */? wildcards. class/interface/enum keywords accepted. |
| Member specs | *;, <methods>;, <fields>;, <init>[(args)], [type ]name[(args)]. Bare name; matches any overload or field. Types are Java types (void, primitives, */***, dotted classes, [] arrays, inner classes as Outer$Inner); ... matches any args. |
- A kept class keeps its fully qualified name (package included); kept members keep names including overrides across the hierarchy, so virtual dispatch can't desync (
<init>and fields stay exact-class). - Member type references always follow renames: keeping
interface APIwhileImplementedAPIscrambles yields e.g.public static final IlII.../lIll... api;withmethodOne()/methodTwo()untouched. - Access reduction won't privatize kept members; shrinking treats them as roots.
- Not supported (silently out of scope, not errors):
extends/implementsclauses,-keepnames/-keepclasseswithmembersvariants. - Malformed rules abort the run with an error naming the rule.
In JSON profiles and the GUI, rules are plain strings: "keep_rules": ["-keep class com.myapp.Plugin { *; }"] (GUI: Target Scoping section, one per line).
JObfuscate features multi-type numeric constant obfuscation covering int, long, float, and double:
- Integer Splitting:
- Strategy 0: Dynamic XOR decomposition
(val ^ k) ^ k - Strategy 1: Arithmetic addition/subtraction
(val + k) - k - Strategy 2: Arithmetic subtraction/addition
(val - k) + k - Strategy 3: Double arithmetic negation
-(-val) - Strategy 4: 3-way XOR chain
((val ^ k1) ^ k2) ^ (k1 ^ k2)
- Strategy 0: Dynamic XOR decomposition
- 64-bit Long Splitting:
- 64-bit entropy masks with
lxor,ladd,lsub, andlnegsequences.
- 64-bit entropy masks with
- Floating-Point Deconstruction:
float: Binary bit pattern decomposition with runtime IEEE-754 reconstruction viajava.lang.Float.intBitsToFloat(intBits).double: 64-bit binary bit pattern decomposition with runtime IEEE-754 reconstruction viajava.lang.Double.longBitsToDouble(longBits).
A lightweight, native cross-platform GUI built with Tauri 2.0, React 19, TypeScript, and Tailwind CSS.
- Preset Profiles: One-click switching between
Max Security,Balanced Production,Stealth (Strings Only), andLibrary / API Mode. - Custom Profile Management: Create, save, load, and export configuration profiles directly to/from JSON.
- Visual Archive Drag & Drop: Select or drop target
.jarfiles with automatic output filename derivation (app_obf.jar). - Comprehensive Scoping Controls: Interactive tags for
-ppackage scoping,-xpackage exclusions,--exclude-classes, keep rules (--keep/--keep-file), and shaded dependency filtering. - Fine-Grained Pass Customization: Interactive toggles and intensity sliders for all 12 obfuscation passes.
- Live Telemetry & Pipeline Stepper: Real-time status cards, per-pass duration tracking, and NDJSON live streaming console.
To launch the desktop GUI:
cd gui
npm install
npm run tauri dev- C++ Compiler: MSVC (Visual Studio 2022 / Community 2026), GCC 11+, or Clang 13+ with C++20 support.
- CMake: Version 3.20 or newer.
- Java Development Kit: JDK 8 through 25 (JDK 25 recommended for full language test coverage).
# Generate build configuration
cmake -B build -DCMAKE_BUILD_TYPE=Release
# Build obfuscator core library and CLI
cmake --build build --config ReleaseThe resulting binary will be located at:
- Windows:
build/core/Release/obfuscator_cli.exe - Linux / macOS:
build/core/obfuscator_cli
The project includes an automated test harness covering all passes, secrecy verification, decompiler constant pool cleanliness, and bytecode compatibility from Java 8 to 25:
powershell -ExecutionPolicy Bypass -File tests\run_tests.ps1or
./tests/run_tests.shAll 60 test suites run end-to-end, validating:
- Full obfuscation pass pipeline.
- Production secrecy and string erasure (incl. stack-split keys).
- Pass toggles (
--no-rename,--no-strings,--no-control-flow,--no-reflection,--disable-access-reduction, etc.). - MBA opaque predicates, exception dispatch on/off, and control-flow flattening.
- Package scoping, exclusion, root flattening, and access reduction.
- Keep rules (
--keep,--keepclassmembers,--keep-file) across shrinking and renaming, incl. kept interfaces with scrambled implementations. - Shrinking/tree-shaking, class encryption with in-memory execution,
invokedynamic/condyindirection, and decompiler deterrence. - Kotlin sample app (kotlinc, downloaded on demand): obfuscation,
kotlin.Metadatastrip/preserve, and stdlib proxying. - Bytecode version compatibility across Target Java 8, 11, 17, 21, and 25.
- Dynamic language feature showcases (
Java11,Java17,Java21,Java25). - Symbol mapping export and stack-trace retracing.
- NDJSON IPC protocol integrity for GUI frontends.
Because every Java application possesses unique bytecode structures—ranging from differing compilers (javac, ECJ, kotlinc, scalac), shaded third-party libraries, complex classloaders, multi-release JARs, and dynamic reflection frameworks (Spring, Quarkus, Jackson, Hibernate)—edge cases and incompatibilities can arise.
Community contributions, bug reports, and bytecode edge-case submissions are welcome!
When submitting an issue, please include:
- Target Java Version: Output of
java -versionand the compilation target version (e.g. Java 8, 11, 17, 21, 25). - Obfuscator Configuration: The exact CLI command line or
config.jsonprofile used. - Decompiler / Runtime Error: The full exception stack trace or decompilation failure log (e.g. Fernflower, CFR, Procyon).
- Minimal Reproducible Sample: A minimal standalone class or small sample JAR that demonstrates the failure. This drastically accelerates diagnosis and fix turnaround.
- Fork the repository and create your branch from
master. - Ensure any new bytecode feature or bugfix includes a corresponding test case in
tests/orsample_app/. - Run the automated test suite to ensure all tests pass:
- Submit your pull request with a clear description of the bytecode transformation affected and the rationale.
A unified GitHub Actions workflow is provided at .github/workflows/build-release.yml to build and package both the C++ CLI (obfuscator_cli) and the Tauri Desktop GUI (JObfuscate) on demand across Windows, Linux, and macOS.
| Platform | CLI Executable | GUI Desktop Installers & Bundles |
|---|---|---|
| Windows | obfuscator_cli-windows-x64.exe |
JObfuscate_<version>_x64_en-US.msiJObfuscate_<version>_x64-setup.exe (NSIS) |
| Linux | obfuscator_cli-linux-x64 |
JObfuscate_<version>_amd64.AppImageJObfuscate_<version>_amd64.deb |
| macOS | obfuscator_cli-macos |
JObfuscate_<version>_x64.dmg |
C. Wang, "A Security Architecture for Survivability Mechanisms," Ph.D. dissertation, Department of Computer Science, University of Virginia, Charlottesville, VA, USA, Oct. 2000, doi: 10.18130/V32N5C.
Copyright © 2026 Liam Kubicki. All rights reserved.
Distributed under a source-available non-commercial License. See LICENSE for details.