OreoObfuscator
Professional Java Bytecode Protection — Complete User Guide
What is OreoObfuscator?
OreoObfuscator protects your compiled Java .jar files so that
competitors or anyone who gets hold of your file
cannot read, copy, or steal your code.
It works at the bytecode level — it modifies the actual
.class files inside your JAR without touching your source code.
The result is a new JAR that runs exactly the same as the original but looks
like total gibberish when decompiled.
What it does to your code
- Renames every class, method, and field to random / Chinese characters
- Encrypts all string constants with AES or XOR so they cannot be read in a decompiler
- Scrambles number constants with math expressions
- Destroys the control flow so decompilers produce unreadable output
- Virtualises methods into a private bytecode interpreter (ELITE only)
Requirements
| Requirement | Version | Notes |
|---|---|---|
| Java JDK / JRE | 21 or newer | OreoObfuscator is compiled with Java 21. Java 8 or 11 will crash immediately. |
| Your input JAR | Any Java version | Certified through stable Java 26 class-file 70. Preview bytecode is handled separately and requires --enable-preview at runtime. |
| Operating System | Windows / Linux / macOS | Pure Java — runs on any platform with Java 21+. |
-Xverify:all tests. See docs/java26-compatibility.md for commands and remaining boundaries.Installation and the protected distribution
Customers receive one self-contained, already-obfuscated executable JAR. They do not need the
OreoObfuscator source tree, Gradle project, clean fat JAR, or private mapping. In this guide the
downloaded file is called OreoObfuscator-protected.jar. If your download has a versioned
name, either keep that name and substitute it in every command, or rename it to the shorter name.
Create a working folder
For example C:\Tools\OreoObfuscator. Put OreoObfuscator-protected.jar,
your license key, and optionally oreo.yml there. Keep target JARs and protected outputs
in separate input and output folders when possible.
Confirm Java 21 or newer
java -version
The first version line must report 21 or newer. The target application may use an older Java version; Java 21+ is required to run the OreoObfuscator engine itself.
Confirm the downloaded executable
java -jar "C:\Tools\OreoObfuscator\OreoObfuscator-protected.jar" --version java -jar "C:\Tools\OreoObfuscator\OreoObfuscator-protected.jar" help
These commands should print the OreoObfuscator version and command list. If the GUI opens,
check that help was placed after the JAR filename.
Activate the license once
java -jar "C:\Tools\OreoObfuscator\OreoObfuscator-protected.jar" license --activate "OREO-your-key"
The saved key is reused by both CLI and GUI. Do not put a real key in a public script, screenshot, issue report, or repository.
Choose how to use OreoObfuscator
Option 1 - Command-line interface (recommended for production and CI)
Use protect after the JAR name. The CLI is reproducible, scriptable, exposes the fixed
seed, and makes the exact input, output, and configuration paths visible. Continue at
CLI workflow.
java -jar OreoObfuscator-protected.jar protect input.jar output-protected.jar -c oreo.yml --seed 123456789
Option 2 - Graphical interface
Launch the protected JAR without arguments, then choose the input, output, configuration, and protection settings visually. Continue at GUI workflow.
java -jar OreoObfuscator-protected.jar
Option 1 - Using the CLI
The CLI is recommended for production because every input is explicit and the command can be reviewed, repeated, logged, and used in CI. The protected executable accepts exactly the same public commands as an unobfuscated build. Supplying a subcommand prevents the GUI from opening.
CLI command structure
java [JVM options] -jar OreoObfuscator-protected.jar COMMAND [command options]
-Xmx2g go before -jar. OreoObfuscator commands and options
go after the executable JAR. Quote every path containing spaces.
# Correct java -Xmx2g -jar "C:\Tools\OreoObfuscator\OreoObfuscator-protected.jar" protect "C:\Apps\My App.jar" "C:\Apps\My App-protected.jar" -c "C:\Apps\oreo.yml" # Display root and command-specific help java -jar OreoObfuscator-protected.jar help java -jar OreoObfuscator-protected.jar protect --help
| Command | Purpose | Modifies files? |
|---|---|---|
protect | Generate a protected JAR from an input JAR. | Writes output, mapping, and optional reports. |
analyze | Inspect class/resource structure before protection. | No. |
verify | Run OreoObfuscator's ASM verification over a JAR. | No. |
mapping | Inspect or query a generated mapping JSON file. | No. |
license | Activate, inspect, or remove the local license. | May update the local license store. |
CLI - Complete protection workflow
Step 1: create a reviewed YAML configuration
Production users should pass -c explicitly. This prevents an unrelated
oreo.yml in the current directory from changing a build. The YAML controls protection
strength, keep rules, shaded-library prefixes, metadata, and mapping output.
java -jar OreoObfuscator-protected.jar analyze "input\MyPlugin.jar" --verbose
Step 2: protect the JAR
The portable one-line form works in PowerShell, Command Prompt, Bash, and most CI runners:
java -Xmx2g -jar OreoObfuscator-protected.jar protect "input\MyPlugin.jar" "output\MyPlugin-protected.jar" -c "oreo.yml" --seed 123456789
PowerShell multiline form:
java -Xmx2g -jar .\OreoObfuscator-protected.jar protect ` ".\input\MyPlugin.jar" ` ".\output\MyPlugin-protected.jar" ` -c ".\oreo.yml" ` --seed 123456789
Bash/Linux/macOS multiline form:
java -Xmx2g -jar ./OreoObfuscator-protected.jar protect \ ./input/MyPlugin.jar \ ./output/MyPlugin-protected.jar \ -c ./oreo.yml \ --seed 123456789
| Argument | Required? | Meaning |
|---|---|---|
INPUT | required | Existing target JAR. It is read, not intentionally modified. |
OUTPUT | required | Destination for the generated protected JAR. Must not be the input path. |
-c, --config CONFIG | optional | Explicit YAML file. Without it, oreo.yml in the working directory is used when present; otherwise defaults apply. |
--seed LONG | optional | Deterministic transformation seed. Same engine, input, config, dependencies, Java environment, and seed produce reproducible decisions. |
--license KEY | optional | Override the saved key and OREO_LICENSE. Avoid this on shared terminals because command histories can expose secrets. |
--no-verify | optional | Disable post-transform verification. Use only for diagnosis; it does not make invalid output valid. |
Step 3: understand success and failure
A successful run prints the selected mode and license, the absolute input path, each transformer, bytecode-verification results, the absolute output path, and a final summary. Treat any failed transformer as a failed release even if an output file exists.
| Protect exit code | Meaning | Action |
|---|---|---|
0 | Pipeline completed successfully. | Continue with independent verification and functional tests. |
1 | License resolution or a transformation failed. | Read the first error and nested cause; do not ship the output. |
2 | Fatal protection exception such as invalid paths or I/O failure. | Correct the cause and rerun from the original input. |
In PowerShell, inspect the process result immediately with $LASTEXITCODE. In Bash use
echo $?. CI must fail the job when the exit code is non-zero.
Step 4: verify the generated JAR independently
# Structural ASM verification java -jar OreoObfuscator-protected.jar verify "output\MyPlugin-protected.jar" # JVM verification and real application startup java -Xverify:all -jar "output\MyPlugin-protected.jar"
verify checks class structure; java -Xverify:all asks the JVM to verify classes
as they load. Neither replaces application tests. Test reflection, resources, services, serialization,
plugins, database access, network initialization, and GUI startup that matter to your program.
Step 5: retain release evidence
- Archive the exact protected JAR and its SHA-256 checksum.
- Keep the original JAR, YAML, seed, engine version, Java version, and dependency set.
- Store
mapping.jsonprivately for stack-trace decoding; never bundle it into the public artifact. - Keep the full CLI log and test results with the release.
CI example using an environment license
# PowerShell
$env:OREO_LICENSE = $env:CI_OREO_LICENSE
java -jar .\OreoObfuscator-protected.jar protect .\build\app.jar .\dist\app-protected.jar -c .\oreo.yml --seed 123456789
if ($LASTEXITCODE -ne 0) { throw "OreoObfuscator failed with exit code $LASTEXITCODE" }
java -jar .\OreoObfuscator-protected.jar verify .\dist\app-protected.jar
if ($LASTEXITCODE -ne 0) { throw "Protected JAR verification failed" }
CLI - Analyze before protecting
Analysis is read-only and helps confirm that the selected input is the intended JAR.
java -jar OreoObfuscator-protected.jar analyze "input\MyPlugin.jar" java -jar OreoObfuscator-protected.jar analyze "input\MyPlugin.jar" --verbose
CLI - Verify an output
The command returns zero when all inspected classes are clean, one for verification issues, and two for fatal loading/I/O errors.
java -jar OreoObfuscator-protected.jar verify "output\MyPlugin-protected.jar"
CLI - Inspect a private mapping
The mapping command requires the JSON path as its first argument. With no additional option it prints
statistics. Use --lookup with the original internal or binary name recorded in the file.
# Statistics java -jar OreoObfuscator-protected.jar mapping "output\mapping.json" --stats # Look up an original class/member name java -jar OreoObfuscator-protected.jar mapping "output\mapping.json" --lookup "com.example.MyPlugin" java -jar OreoObfuscator-protected.jar mapping "output\mapping.json" --lookup "com/example/MyPlugin.onEnable ()V"
CLI - Manage the license
# Activate and save a customer key java -jar OreoObfuscator-protected.jar license --activate "OREO-your-key" # Show customer, tier, and expiry java -jar OreoObfuscator-protected.jar license --info # Remove the locally saved key java -jar OreoObfuscator-protected.jar license --deactivate
On Windows the saved key is under %USERPROFILE%\.oreo\license.key. On Linux/macOS it is
under ~/.oreo/license.key. Deactivation removes the local copy; it does not modify a
protected application JAR.
Option 2 - Using the GUI
The GUI is the easiest workflow for occasional manual builds. It uses the same transformation pipeline and license as the CLI. The protected distribution opens the GUI when it receives no command-line arguments.
1. Start the correct executable
On Windows, opening a terminal is more reliable than double-clicking because it proves which Java installation and which OreoObfuscator JAR are being used:
cd /d C:\Tools\OreoObfuscator java -jar OreoObfuscator-protected.jar
In PowerShell, use Set-Location C:\Tools\OreoObfuscator instead of cd /d.
Double-clicking is also supported when Windows associates .jar files with Java 21+.
2. Activate or confirm the license
Open the License tab. Paste the supplied OREO-... key and click
Activate, or confirm that the expected customer, tier, and expiry are displayed.
The license is saved under %USERPROFILE%\.oreo\license.key and is shared with the CLI.
3. Select the target application JAR
Return to Protect. Drag the application/plugin JAR onto the drop zone or click the zone and browse to it. The green selection panel shows the complete path. Read that path before continuing.
MyPlugin.jar. Do not select
OreoObfuscator-protected.jar, an older *-protected.jar, or a previous output
unless you intentionally want to obfuscate that file again. The GUI never searches for or chooses
a JAR automatically; the input comes from the file browser or drag-and-drop.
4. Choose a separate output JAR
The GUI proposes a name ending in -protected.jar. Use Browse to choose
another location when needed. Confirm that input and output are different files and that the output
directory is writable. When changing input files, recheck the output field because a previously typed
output path may remain visible.
5. Choose how settings are supplied
| Mode | When to use it | Important behavior |
|---|---|---|
| YAML config | Production builds, fat JARs, reflection, frameworks, repeatable releases | The selected file takes complete priority over the Settings tab. |
| Settings tab | Simple experiments and small applications | Used only when the Config File field is empty. |
For production, click Browse beside Config File and select your reviewed
oreo.yml. Keep rules and library prefixes are available only through YAML and are often
required for reflection, service loading, shaded dependencies, Spring, and Minecraft plugins.
6. Configure protection
If no YAML file is selected, open Settings and configure renaming, strings, numbers, control flow, virtualization, and Minecraft support. Stronger settings increase transformation time, output size, startup cost, and compatibility risk. Test incrementally before selecting an extreme preset.
7. Keep verification enabled
The control is labelled Skip bytecode verification. For a release build, leave it unchecked so OreoObfuscator verifies generated classes before reporting success. Temporarily skipping verification is a diagnostic measure, not proof that an output is safe to ship.
8. Protect and read the complete log
Click PROTECT JAR. Wait until the button becomes available again. A created file alone does not prove success: check the console for the exact input/output paths, transformer failures, verification issues, and the final success summary. Copy the complete log when requesting support.
9. Test the generated JAR outside the GUI
Close or minimize OreoObfuscator and launch the protected application in a separate terminal:
java -Xverify:all -jar "C:\path\to\MyPlugin-protected.jar"
For a server plugin, place a copy in a clean test server and inspect the full startup log. For a GUI application, verify startup and every important workflow. For libraries, run the consuming project's automated tests. An expected headless-display exception is different from a bytecode, linkage, or class-initialization failure.
GUI tabs
| Tab | Purpose |
|---|---|
| Protect | Select exact input/output/config paths, run protection, and read live logs. |
| Settings | Configure protections when no YAML file is loaded. |
| License | Activate, inspect, or deactivate the shared offline license. |
GUI field reference
| Field | Meaning | Release check |
|---|---|---|
| Drop zone / Input | The original target JAR read by the engine. | Full path points to the intended application. |
| Output JAR | The newly generated protected artifact. | Different path from input; old output archived or intentionally replaced. |
| Config File | Optional YAML configuration. | Correct project-specific file; blank only when GUI settings are intended. |
| Skip bytecode verification | Disables the engine's post-transform ASM checks. | Unchecked for normal release builds. |
| Console | Progress, warnings, errors, paths, and final result. | No failed transformer and no verification issue. |
GUI — Settings Tab
Six sections. Each can be independently enabled or disabled. Changes take effect on the next Protect run. These settings are used only when no Config File is selected in the Protect tab.
See the Config File Reference below for a full explanation of each option — the Settings tab exposes the same controls.
GUI — License Tab
Shows a card with your name, tier, and expiry date. Available actions:
- Activate — paste a
OREO-...key and click Activate - Deactivate — removes the saved key from your machine
The key is saved to %USERPROFILE%\.oreo\license.key and loads automatically
every time you open OreoObfuscator.
Config File — Overview
A .yml file that stores all your obfuscation settings.
Save it once, reuse it across every build.
- CLI: pass with
-c yourfile.yml - GUI: click Browse in the Config File row on the Protect tab
If you do not specify a config file, OreoObfuscator looks for oreo.yml
in the current working directory. If that doesn't exist either, it uses built-in defaults
(everything enabled at a moderate level with ASCII name mode).
keepRules and
libraryPrefixes — two settings that are critical for fat JARs and
for telling OreoObfuscator not to rename your public API classes.
Config — mode
A global preset that sets sensible defaults for all transformers at once. Individual sections below always override the preset.
mode: STANDARD # LITE | STANDARD | AGGRESSIVE | EXTREME
| Value | What it does |
|---|---|
LITE | Renaming only. No string/number obfuscation. No control flow. Fastest, minimal protection. |
STANDARD | Renaming + string encryption + number obfuscation. Control flow at HEAVY level. Good balance. |
AGGRESSIVE | Everything in STANDARD plus EXTREME control flow and exception-based flow. |
EXTREME | Everything on at maximum. Enable virtualisation separately if you have an ELITE key. |
Config — rename
Controls identifier renaming — the most impactful layer. Without renaming, decompiled code is immediately readable.
rename: enabled: true # master switch packages: true # rename package names classes: true # rename class names methods: true # rename method names fields: true # rename field names localVariables: true # strip local variable debug names parameters: true # strip parameter debug names nameMode: CHINESE # ASCII | UNICODE | CHINESE
| Key | Values | Description |
|---|---|---|
| enabled | true / false | Master switch. Set to false to skip renaming entirely. |
| packages | true / false | Renames package segments (e.g. com.example.plugin → 龘.金.天) |
| classes | true / false | Renames class simple names |
| methods | true / false | Renames method names. Override groups are handled automatically — all overriding methods across the hierarchy get the same new name. |
| fields | true / false | Renames field names. Inherited field access via subclass is handled correctly. |
| localVariables | true / false | Strips local variable debug info from bytecode. Does not affect runtime — only affects what debuggers and decompilers show. |
| parameters | true / false | Strips parameter names from bytecode. Same as above. |
| nameMode | ASCII / UNICODE / CHINESE | Style of generated names. See table below. |
nameMode
| Mode | Example output | Notes |
|---|---|---|
ASCII | a, aa, ab, b | Short ASCII letters. Safest for all environments. Least visually intimidating. |
UNICODE | ꀀ, ꀁ, ꀂ | Invisible or look-alike Unicode codepoints. Breaks many decompiler UIs. |
CHINESE | 龘, 金人, 天地 | Semantic Chinese CJK characters. Decompiled code looks like foreign text. Most psychologically off-putting. |
<init>), static initialisers (<clinit>),
Object methods (equals, hashCode, toString, etc.),
enum values() and valueOf(),
methods that override external library interfaces (Bukkit API, etc.),
and anything listed in keepRules.
Config — unicode
Controls which Unicode character set is used when nameMode is UNICODE or CHINESE.
unicode: enabled: true charset: CJK # CJK | HANGUL | HIRAGANA | KATAKANA | MIXED
| charset | Characters | Notes |
|---|---|---|
| CJK | Chinese / Japanese / Korean ideographs | Default. Works with CHINESE nameMode to produce meaningful-looking Chinese names. |
| HANGUL | Korean Hangul syllables | Korean-looking names. |
| HIRAGANA | Japanese Hiragana | Japanese-looking names. |
| KATAKANA | Japanese Katakana | Japanese-looking names. |
| MIXED | Mix of all the above | Random mix of all Unicode ranges. |
Config — strings
Encrypts every String constant in your code.
Without this, a decompiler shows passwords, API keys, URLs, and error messages in plain text.
With this enabled they are stored encrypted and decrypted at runtime when first used.
strings: enabled: true encryption: MIXED # XOR | AES | MIXED perClassKeys: true # unique AES key per class lazyDecrypt: true # decrypt on first access, not at startup
| Key | Values | Description |
|---|---|---|
| enabled | true / false | Master switch. |
| encryption | XOR / AES / MIXED | XOR: fast, zero overhead, slightly simpler. AES: AES-128, works for all Unicode strings including emoji. MIXED: randomly applies XOR or AES per string — no single pattern to attack. |
| perClassKeys | true / false | When true, each class gets its own randomly-generated AES key. Extracting one key does not help decrypt other classes. |
| lazyDecrypt | true / false | When true, strings are decrypted on first access. When false, all strings decrypt at class-load time (slightly faster after first load, slightly slower startup). |
encryption: MIXED with perClassKeys: true.
Config — numbers
Replaces integer, long, float, and double constants with equivalent arithmetic expressions.
Instead of 42 a decompiler sees something like ((0x2F ^ 0x15) + 4).
Near-zero runtime cost.
numbers: enabled: true
Config — controlFlow PRO+
Restructures the logic flow inside every method. The code does exactly the same thing
at runtime, but decompilers (Fernflower, CFR, Procyon, jadx) either crash or produce
complete gibberish full of goto statements.
controlFlow: enabled: true level: HEAVY # LIGHT | MEDIUM | HEAVY | EXTREME opaquePredicates: true # always-true/false conditions fakeBranches: true # unreachable code blocks deadBranches: true # dead code injection switchFlattening: false # convert if/else chains into switch dispatch stateMachines: false # convert loops into state machine dispatchers exceptionBasedFlow: false # use try/catch for unconditional jumps
| Key | Values | Description |
|---|---|---|
| enabled | true / false | Master switch. Requires PRO or ELITE license — silently ignored on STARTER. |
| level | LIGHT / MEDIUM / HEAVY / EXTREME | How aggressively methods are restructured. See table below. |
| opaquePredicates | true / false | Inserts conditions that always evaluate the same way but look unpredictable to analysis tools. |
| fakeBranches | true / false | Adds dead code branches that look real but can never execute. |
| deadBranches | true / false | Inserts unreachable code blocks to confuse flow-graph analysis. |
| switchFlattening | true / false | Converts structured if/else chains into switch-dispatch loops. Harder to decompile. |
| stateMachines | true / false | Converts loop bodies into state machine dispatchers. Most aggressive structural change. |
| exceptionBasedFlow | true / false | Uses throw/catch as unconditional jumps. Most disruptive technique. Test your plugin after enabling. |
Level comparison
| Level | Runtime overhead | What decompilers produce |
|---|---|---|
LIGHT | None | Messy but sometimes readable |
MEDIUM | Negligible | Hard to follow, lots of gotos |
HEAVY | Very small | Mostly unreadable — recommended |
EXTREME | Small — test first | Decompiler crashes or total gibberish |
Config — minecraft
Enable this for every Minecraft plugin.
OreoObfuscator reads your plugin.yml (or paper-plugin.yml)
to find your main class, commands, and listeners, and automatically keeps their names
so Bukkit / Paper / Velocity can still discover them at runtime.
minecraft: enabled: true platform: PAPER # BUKKIT | SPIGOT | PAPER | VELOCITY keepEventListeners: true # keep Listener classes + @EventHandler methods keepCommands: true # keep command executor classes keepPermissions: true # keep permission nodes keepSerializableTypes: true # keep ConfigurationSerializable classes
| Key | Values | Description |
|---|---|---|
| enabled | true / false | Master switch. Must be true for Minecraft plugins. |
| platform | BUKKIT / SPIGOT / PAPER / VELOCITY | Which server API to target. Affects how plugin.yml is parsed and which keep rules are applied automatically. |
| keepEventListeners | true / false | Keeps classes implementing Listener and their @EventHandler methods so events fire. |
| keepCommands | true / false | Keeps command executor classes listed in plugin.yml. |
| keepPermissions | true / false | Keeps permission-related declarations. |
| keepSerializableTypes | true / false | Keeps classes implementing ConfigurationSerializable — needed for custom YAML types. |
Config — virtualization ELITE
The strongest available protection. Selected methods are compiled into a private custom bytecode format and executed at runtime by an embedded interpreter. No Java decompiler can reconstruct these methods — they simply do not exist as JVM bytecode.
virtualization: enabled: false methods: [] # leave empty to auto-select, or list specific methods # Specific method selector format: "internalClassName.methodName" # Example: virtualization: enabled: true methods: - "com/example/plugin/LicenseChecker.validate" - "com/example/plugin/AntiCheat.processPacket"
Config — keepRules
A list of class names that must never be renamed. Use this for any class that is referenced by name at runtime — through reflection, configuration files, service loaders, MANIFEST.MF Main-Class, or any framework that looks up classes by string name.
Use dot-separated class names (Java style). Both formats are accepted.
keepRules: - com.example.plugin.MyPlugin # main plugin class - com.example.plugin.api.PublicAPI # API class used by other plugins - com.example.plugin.gui.MyGUI # referenced in logback.xml by string name - com.example.plugin.Commands # referenced from config file
main(String[]) method.
Keep rules do not disable bytecode transformations such as string or control-flow obfuscation.
Config — libraryPrefixes
A list of internal class name prefixes (slash-separated) that identify third-party libraries bundled inside your JAR (shaded / fat JAR dependencies). Classes matching these prefixes are loaded for hierarchy analysis but are never renamed or transformed — they pass through to the output unchanged.
Without this, OreoObfuscator treats shaded libraries as your own code and tries to obfuscate them. This causes two problems: the output JAR is much larger (library code gets string/number-expanded), and large library classes with thousands of static fields can hit the JVM's 64 KB method size limit.
libraryPrefixes: - org/objectweb/asm # ASM bytecode library - org/yaml/snakeyaml # SnakeYAML - org/slf4j # SLF4J logging API - ch/qos/logback # Logback - com/google/gson # Gson - com/google/protobuf # Protobuf - info/picocli # PicoCLI - dev/yourname/libs # your shaded dependencies (any prefix works) - org/sqlite # SQLite JDBC
libraryPrefixes. Your own code should not be.
Config — Output Options
Fine-grained control over what debug metadata is preserved or stripped.
mappingOutput: "mapping.json" # where to write the name mapping file (relative to output JAR) keepLineNumbers: false # keep line number tables (useful for stack trace readability) keepSourceFile: false # keep SourceFile attributes (shows original .java filename) keepLocalVars: false # keep local variable debug tables keepParamNames: false # keep method parameter name attributes
| Key | Default | Description |
|---|---|---|
| mappingOutput | "mapping.json" | Path to the mapping file written after obfuscation. Set to empty string "" to disable. Used to decode obfuscated stack traces back to original names. |
| keepLineNumbers | false | When true, line numbers are preserved in the output. Useful if you want stack traces to show line numbers for debugging. Gives away code structure so leave false for production. |
| keepSourceFile | false | When true, the original .java filename attribute is kept in class files. Leave false. |
| keepLocalVars | false | When true, local variable debug tables are preserved. These show variable names in debuggers. Leave false. |
| keepParamNames | false | When true, parameter name attributes are kept. Frameworks that use reflection-based injection may need this. Otherwise leave false. |
Config — Full Example
A complete oreo.yml showing every available key:
# ───────────────────────────────────────────────────────────────────── # oreo.yml — complete example # Pass to CLI with: -c oreo.yml # Load in GUI via: Config File → Browse → select this file # ───────────────────────────────────────────────────────────────────── # Global preset (individual sections below override this) mode: STANDARD # LITE | STANDARD | AGGRESSIVE | EXTREME # ── Identifier renaming ─────────────────────────────────────────────── rename: enabled: true packages: true classes: true methods: true fields: true localVariables: true parameters: true nameMode: CHINESE # ASCII | UNICODE | CHINESE # ── Unicode character set ───────────────────────────────────────────── unicode: enabled: true charset: CJK # CJK | HANGUL | HIRAGANA | KATAKANA | MIXED # ── String encryption ───────────────────────────────────────────────── strings: enabled: true encryption: MIXED # XOR | AES | MIXED perClassKeys: true lazyDecrypt: true # ── Number obfuscation ──────────────────────────────────────────────── numbers: enabled: true # ── Control-flow obfuscation (PRO+ only) ───────────────────────────── controlFlow: enabled: true level: HEAVY # LIGHT | MEDIUM | HEAVY | EXTREME opaquePredicates: true fakeBranches: true deadBranches: true switchFlattening: false stateMachines: false exceptionBasedFlow: false # test your plugin before enabling # ── Minecraft plugin mode ───────────────────────────────────────────── minecraft: enabled: true platform: PAPER # BUKKIT | SPIGOT | PAPER | VELOCITY keepEventListeners: true keepCommands: true keepPermissions: true keepSerializableTypes: true # ── Bytecode virtualisation (ELITE only) ───────────────────────────── virtualization: enabled: false methods: [] # empty = auto-select; or list "ClassName.methodName" entries # ── Classes that must never be renamed ─────────────────────────────── # Use dot-separated Java class names. keepRules: - com.example.plugin.MyPlugin - com.example.plugin.api.PublicAPI # ── Shaded library prefixes (slash-separated internal names) ───────── # These classes pass through unchanged — never renamed or transformed. libraryPrefixes: - org/objectweb/asm - org/yaml/snakeyaml - com/google/gson - dev/yourname/libs # ── Output options ──────────────────────────────────────────────────── mappingOutput: "mapping.json" keepLineNumbers: false keepSourceFile: false keepLocalVars: false keepParamNames: false
License Tiers
OreoObfuscator uses an offline RSA-2048 license system. No internet connection required — the key is verified locally using a public key baked into the binary. No one can fake or crack a license key.
| Feature | STARTER | PRO | ELITE |
|---|---|---|---|
| Renaming (classes, methods, fields, packages) | ✔ | ✔ | ✔ |
| String Encryption (XOR / AES / MIXED) | ✔ | ✔ | ✔ |
| Number Obfuscation | ✔ | ✔ | ✔ |
| Minecraft Mode (auto-keep) | ✔ | ✔ | ✔ |
| GUI + CLI | ✔ | ✔ | ✔ |
| Config file support (keepRules, libraryPrefixes) | ✔ | ✔ | ✔ |
| Control-Flow Obfuscation | ✘ | ✔ | ✔ |
| CHINESE / UNICODE name modes | ✘ | ✔ | ✔ |
| Bytecode Virtualisation | ✘ | ✘ | ✔ |
Activating a License Key
Via the GUI
Open the License tab
Click License at the top of the window.
Paste your key
Your key looks like OREO-eyJuYW1lIjoiSm9obi....
Click the input box and paste the full key.
Click Activate
If valid, a green confirmation shows your name, tier, and expiry.
The key is saved to %USERPROFILE%\.oreo\license.key
and auto-loads every time you open OreoObfuscator.
%USERPROFILE%\.oreo\license.key from your old machine to the same location
on the new one, or just re-activate your key via the License tab.
"Java Exception Has Occurred" when double-clicking
Cause: Windows is opening the JAR with Java 8. OreoObfuscator requires Java 21+.
Fix:
- Double-click
fix_jar_association.regincluded with your download. - Click Yes to both Windows prompts.
- Double-click the JAR again.
Or always launch from a terminal:
java -jar "C:\path\to\OreoObfuscator-protected.jar"
java -version. You need version "21" or higher.
Download Java 21 free from adoptium.net.
Plugin crashes with "is not an enum class"
[ERROR] SomeGibberishName is not an enum class
Cause: An old build of OreoObfuscator was renaming enum values()
methods. Java's internals call values() by that exact name via reflection.
Fix: This is fixed in v1.0.0. Re-obfuscate with the current version.
Minecraft plugin does not load after obfuscation
Cannot find main class
Symptom: Server says "Cannot find main class 'com.example.MyPlugin'"
Fix: Enable Minecraft Mode in Settings (or in your config file: minecraft.enabled: true).
Commands don't respond
Fix: minecraft.keepCommands: true
Events / listeners not firing
Fix: minecraft.keepEventListeners: true
Custom YAML types broken
Fix: minecraft.keepSerializableTypes: true
Obfuscation skips some classes with "method too large" warning
Symptom: Console shows [WARN] ControlFlowObfuscation: skipping 'some/Library'
— method already too large
Cause: Your JAR contains a shaded library (e.g. MySQL JDBC, SQLite, Protobuf)
whose <clinit> initialises thousands of static fields.
After string/number expansion the method exceeds the JVM's hard 64 KB code-attribute limit.
Fix: Add the library's package prefix to libraryPrefixes in your config
so it passes through untouched:
libraryPrefixes: - com/google/protobuf - dev/yourname/libs/mysql - org/sqlite
OreoObfuscator gracefully handles the case where these prefixes are not set —
affected classes are written with their original (non-renamed) bytes and a warning is logged —
but adding them to libraryPrefixes is the correct permanent solution.
Bytecode verification errors
A verification error means the generated artifact has not passed the configured safety gate. Do not distribute it merely because a file was written or because one startup path appears to work.
Diagnosis order:
- Keep the original input and full log. Find the first failing class and root exception.
- Rerun with the same YAML and seed to confirm reproducibility.
- Lower control flow from
EXTREMEtoHEAVYor disable the transformer named in the failure to isolate the interaction. - Add precise keep rules or library prefixes when the failing code relies on reflection, services, generated metadata, or shaded dependencies.
- Run both
oreo verifyandjava -Xverify:allafter every configuration change.
Do not obfuscate the distributed engine again
Customers already receive OreoObfuscator-protected.jar. Use it as the executable engine;
do not select it as the target input and do not run the protected engine through itself again. Doing so
produces a nested transformation that is unsupported for customer use and makes support evidence ambiguous.
If the GUI log says the input is OreoObfuscator-protected.jar, cancel the run and select
the application or plugin JAR you intended to protect. The engine executable belongs after
java -jar; the target belongs after the protect command:
java -jar OreoObfuscator-protected.jar protect MyApplication.jar MyApplication-protected.jar -c oreo.yml