OreoObfuscator

Professional Java Bytecode Protection — Complete User Guide

Java 21+ Minecraft Plugins GUI & CLI Offline License ASM Bytecode

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

RequirementVersionNotes
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+.
Java 26 certification Stable Java 26 applications, ServiceLoader plugins, Paper linkage, records, sealed classes, lambdas, invokedynamic, nestmates and JPMS are covered by automated -Xverify:all tests. See docs/java26-compatibility.md for commands and remaining boundaries.
⚠ Java Version If you get "Java Exception Has Occurred" when double-clicking the JAR, Windows is opening it with an old Java 8 installation. See Troubleshooting for the 30-second fix.

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.

ℹ The protected build has the same public interface Obfuscating OreoObfuscator changes its internal class and method names; it does not change the commands shown in this guide. The same protected JAR starts the CLI when a subcommand is supplied and starts the GUI when launched without arguments.

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
⚠ Never overwrite the input JAR Always use a different output path. Keep the original JAR until the protected build has passed bytecode verification and its own functional tests. OreoObfuscator does not modify Java source files.

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]
ℹ Argument order matters JVM options such as -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
CommandPurposeModifies files?
protectGenerate a protected JAR from an input JAR.Writes output, mapping, and optional reports.
analyzeInspect class/resource structure before protection.No.
verifyRun OreoObfuscator's ASM verification over a JAR.No.
mappingInspect or query a generated mapping JSON file.No.
licenseActivate, 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
ArgumentRequired?Meaning
INPUTrequiredExisting target JAR. It is read, not intentionally modified.
OUTPUTrequiredDestination for the generated protected JAR. Must not be the input path.
-c, --config CONFIGoptionalExplicit YAML file. Without it, oreo.yml in the working directory is used when present; otherwise defaults apply.
--seed LONGoptionalDeterministic transformation seed. Same engine, input, config, dependencies, Java environment, and seed produce reproducible decisions.
--license KEYoptionalOverride the saved key and OREO_LICENSE. Avoid this on shared terminals because command histories can expose secrets.
--no-verifyoptionalDisable 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 codeMeaningAction
0Pipeline completed successfully.Continue with independent verification and functional tests.
1License resolution or a transformation failed.Read the first error and nested cause; do not ship the output.
2Fatal 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

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.

⚠ Select the application, not OreoObfuscator The input must be the JAR you want to protect, such as 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

ModeWhen to use itImportant behavior
YAML configProduction builds, fat JARs, reflection, frameworks, repeatable releasesThe selected file takes complete priority over the Settings tab.
Settings tabSimple experiments and small applicationsUsed 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

TabPurpose
ProtectSelect exact input/output/config paths, run protection, and read live logs.
SettingsConfigure protections when no YAML file is loaded.
LicenseActivate, inspect, or deactivate the shared offline license.

GUI field reference

FieldMeaningRelease check
Drop zone / InputThe original target JAR read by the engine.Full path points to the intended application.
Output JARThe newly generated protected artifact.Different path from input; old output archived or intentionally replaced.
Config FileOptional YAML configuration.Correct project-specific file; blank only when GUI settings are intended.
Skip bytecode verificationDisables the engine's post-transform ASM checks.Unchecked for normal release builds.
ConsoleProgress, 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:

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.

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).

✔ Always use a config file for production The config file is the only place to set 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
ValueWhat it does
LITERenaming only. No string/number obfuscation. No control flow. Fastest, minimal protection.
STANDARDRenaming + string encryption + number obfuscation. Control flow at HEAVY level. Good balance.
AGGRESSIVEEverything in STANDARD plus EXTREME control flow and exception-based flow.
EXTREMEEverything 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
KeyValuesDescription
enabledtrue / falseMaster switch. Set to false to skip renaming entirely.
packagestrue / falseRenames package segments (e.g. com.example.plugin龘.金.天)
classestrue / falseRenames class simple names
methodstrue / falseRenames method names. Override groups are handled automatically — all overriding methods across the hierarchy get the same new name.
fieldstrue / falseRenames field names. Inherited field access via subclass is handled correctly.
localVariablestrue / falseStrips local variable debug info from bytecode. Does not affect runtime — only affects what debuggers and decompilers show.
parameterstrue / falseStrips parameter names from bytecode. Same as above.
nameModeASCII / UNICODE / CHINESEStyle of generated names. See table below.

nameMode

ModeExample outputNotes
ASCIIa, aa, ab, bShort 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.
ℹ What is never renamed OreoObfuscator automatically keeps safe things that would break at runtime: constructors (<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
charsetCharactersNotes
CJKChinese / Japanese / Korean ideographsDefault. Works with CHINESE nameMode to produce meaningful-looking Chinese names.
HANGULKorean Hangul syllablesKorean-looking names.
HIRAGANAJapanese HiraganaJapanese-looking names.
KATAKANAJapanese KatakanaJapanese-looking names.
MIXEDMix of all the aboveRandom 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
KeyValuesDescription
enabledtrue / falseMaster switch.
encryptionXOR / AES / MIXEDXOR: 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.
perClassKeystrue / falseWhen true, each class gets its own randomly-generated AES key. Extracting one key does not help decrypt other classes.
lazyDecrypttrue / falseWhen true, strings are decrypted on first access. When false, all strings decrypt at class-load time (slightly faster after first load, slightly slower startup).
✔ Recommended Use 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
KeyValuesDescription
enabledtrue / falseMaster switch. Requires PRO or ELITE license — silently ignored on STARTER.
levelLIGHT / MEDIUM / HEAVY / EXTREMEHow aggressively methods are restructured. See table below.
opaquePredicatestrue / falseInserts conditions that always evaluate the same way but look unpredictable to analysis tools.
fakeBranchestrue / falseAdds dead code branches that look real but can never execute.
deadBranchestrue / falseInserts unreachable code blocks to confuse flow-graph analysis.
switchFlatteningtrue / falseConverts structured if/else chains into switch-dispatch loops. Harder to decompile.
stateMachinestrue / falseConverts loop bodies into state machine dispatchers. Most aggressive structural change.
exceptionBasedFlowtrue / falseUses throw/catch as unconditional jumps. Most disruptive technique. Test your plugin after enabling.

Level comparison

LevelRuntime overheadWhat decompilers produce
LIGHTNoneMessy but sometimes readable
MEDIUMNegligibleHard to follow, lots of gotos
HEAVYVery smallMostly unreadable — recommended
EXTREMESmall — test firstDecompiler 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
KeyValuesDescription
enabledtrue / falseMaster switch. Must be true for Minecraft plugins.
platformBUKKIT / SPIGOT / PAPER / VELOCITYWhich server API to target. Affects how plugin.yml is parsed and which keep rules are applied automatically.
keepEventListenerstrue / falseKeeps classes implementing Listener and their @EventHandler methods so events fire.
keepCommandstrue / falseKeeps command executor classes listed in plugin.yml.
keepPermissionstrue / falseKeeps permission-related declarations.
keepSerializableTypestrue / falseKeeps classes implementing ConfigurationSerializable — needed for custom YAML types.
⚠ Plugin won't load without Minecraft Mode Without it the server cannot find your main class. You will see "Cannot find main class" or "InvalidPluginException".

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"
⚠ Requires ELITE license Silently ignored on STARTER and PRO keys.

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
ℹ What keepRules does exactly It keeps the class and its members from identifier renaming. Manifest entry classes are detected automatically, including the JVM-required main(String[]) method. Keep rules do not disable bytecode transformations such as string or control-flow obfuscation.
⚠ keepRules is only available in the config file The GUI Settings tab has no field for keep rules. To use keep rules from the GUI, click Browse in the Config File row and load your YAML file.

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
✔ Rule of thumb Any package that comes from a Maven/Gradle dependency that you shade/shadow into your JAR should be in libraryPrefixes. Your own code should not be.
⚠ libraryPrefixes is only available in the config file Same as keepRules — no GUI field exists for this. Load a config file from the Protect tab.

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
KeyDefaultDescription
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.
keepLineNumbersfalseWhen 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.
keepSourceFilefalseWhen true, the original .java filename attribute is kept in class files. Leave false.
keepLocalVarsfalseWhen true, local variable debug tables are preserved. These show variable names in debuggers. Leave false.
keepParamNamesfalseWhen 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.

ℹ Moving to a new computer Copy %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:

  1. Double-click fix_jar_association.reg included with your download.
  2. Click Yes to both Windows prompts.
  3. Double-click the JAR again.

Or always launch from a terminal:

java -jar "C:\path\to\OreoObfuscator-protected.jar"
ℹ Check your Java version Open a terminal and run 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:

  1. Keep the original input and full log. Find the first failing class and root exception.
  2. Rerun with the same YAML and seed to confirm reproducibility.
  3. Lower control flow from EXTREME to HEAVY or disable the transformer named in the failure to isolate the interaction.
  4. Add precise keep rules or library prefixes when the failing code relies on reflection, services, generated metadata, or shaded dependencies.
  5. Run both oreo verify and java -Xverify:all after every configuration change.
⚠ --no-verify is not a fix Skipping verification is useful only to collect additional diagnostic evidence. It must not be used to hide a verifier failure in a release build.

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
ℹ Publisher-only process Building and self-obfuscating OreoObfuscator itself requires the private clean build, its dedicated self-protection configuration, regression suite, and release pipeline. Those files are intentionally not part of the customer distribution.