Dev.to Security ๐Ÿ” Cybersecurity ๐Ÿ‘ 0 ๐Ÿ“– 5 min read

How to obfuscate a Minecraft plugin (free, step by step)

Assume your premium plugin will be decompiled the day it goes on sale. Anyone can open the jar in a decompiler and read your code like the source, including the license check you spent an evening on. Obfuscation won't m

How to obfuscate a Minecraft plugin (free, step by step)

Assume your premium plugin will be decompiled the day it goes on sale. Anyone can open the jar in a decompiler and read your code like the source, including the license check you spent an evening on.

Obfuscation won't make that impossible. It turns readable code into a puzzle, so copying your logic or patching out a check takes far more work. The catch: a careless obfuscation pass is the fastest way to ship a plugin that doesn't load. Paper and Spigot find your main class by name and call your event handlers by reflection. Rename the wrong thing and the server throws ClassNotFoundException at startup.

This guide shows two ways to do it safely: the quick way, with a free hosted obfuscator that writes the keep rules for you, and the by-hand way with ProGuard. Then a checklist to test the jar before you sell it.

A license check before and after obfuscation, as a decompiler shows it

What obfuscation does, and what it doesn't

It does: rename your classes, methods and fields to a, b, c, encrypt string constants, and scramble control flow. A decompiler still produces Java, but it reads like noise.

It doesn't: stop someone determined, or stop a buyer from sharing the jar. It raises the cost of reading and patching your code. For sharing you need a license key per buyer (more at the end). The two work best together: obfuscation hides the check, and the license makes a shared jar useless.

The quick way: free hosted obfuscation

I build SunLicense, a licensing service for plugin and app sellers. Its free plan includes 3 obfuscation runs a month, which covers a release or two. It runs ProGuard, yGuard and Skidfuscator on its servers, and it reads your jar to find what must keep its name, so you don't write keep rules.

Step 1: Create a free account

Sign up with Google, GitHub, Discord or email. No credit card.

Step 2: Developers โ†’ Obfuscation โ†’ Java

Open Obfuscation under Developers in the sidebar and pick Java. (The same page handles JavaScript, Python and Lua for FiveM, if you sell those too.)

The Obfuscation page: choose Java, JavaScript/TypeScript, Lua or Python

Step 3: Pick Maximum or ProGuard

Engine What it does Pick it when
Maximum (recommended) ProGuard renames, then Skidfuscator adds control flow and string encryption You want the strongest protection and will test the result
ProGuard Renames classes, methods and fields You want the safe standard, or Maximum broke something
yGuard Renames only You need the most compatible option
Skidfuscator Control flow and strings, keeps names You want flow and string protection without renaming

Start with Maximum. If your plugin leans heavily on reflection (NMS, Gson models, a command framework) and something misbehaves in testing, run the same jar again with ProGuard.

Choose an obfuscator: Maximum, ProGuard, yGuard and Skidfuscator

Step 4: Upload the built jar

Build the jar you actually ship (with Shadow, ./gradlew shadowJar) and drop it in. A .jar or a .zip of several jars, up to 100 MB, Java up to 25.

SunLicense reads the jar and keeps the names that must survive: the main class in plugin.yml, paper-plugin.yml or bungee.yml, velocity-plugin.json, Fabric and Forge entry points and mixins, META-INF/services, @EventHandler and @Subscribe methods, enums and serialisable types.

One detail that saves a lot of pain: if you sell on BuiltByBit or Voxel Shop with a download placeholder (a string like %%__SUNLICENSE__%% the store replaces with the buyer's key), string encryption would hide that string from the store. Classes that hold a placeholder are never string-encrypted, so key injection keeps working.

Upload your file: .jar or .zip, up to 100 MB

Optional: Advanced options has a Keep unrenamed list for extra packages or classes (for example com.example.api.**), and lets you add library jars you compile against if the report mentions missing classes.

Step 5: Run, then download three files

Choose Obfuscate and watch the live log. It runs as a background task, so you can close the page and come back; results are kept for 7 days and stay on the History tab. You get:

  1. The obfuscated jar: the one you upload to your store.
  2. The mapping file: keep it for every release. When a buyer sends a stack trace full of a.b.c, ProGuard's retrace tool turns it back into your real names.
  3. A short report on the run, including any warnings.

Releasing often? The same run works from a release script with the CLI:

sunlicense obfuscate build/libs/MyPlugin-1.0-all.jar --profile release -o dist/

The by-hand way: ProGuard

If you'd rather run it yourself, ProGuard is the safe choice. What you must keep:

Keep Why
The main: class in plugin.yml (and paper-plugin.yml, bungee.yml, velocity-plugin.json) The server loads it by exact name
@EventHandler methods Found by annotation; shrinking removes "unused" methods
ConfigurationSerializable classes Saved configs store the class name
Enum values() / valueOf() Used when reading config values back
Anything loaded by reflection The name is a string in your code

A starting config for a shaded jar:

-injars  build/libs/MyPlugin-1.0-all.jar
-outjars build/obf/MyPlugin-1.0.jar
-libraryjars <java.home>/jmods/java.base.jmod(!**.jar;!module-info.class)
-libraryjars libs/paper-api.jar

# Plugins are loaded by reflection: don't shrink or optimise
-dontshrink
-dontoptimize
-keepattributes *Annotation*,Signature,InnerClasses,EnclosingMethod,SourceFile,LineNumberTable

-keep public class com.example.myplugin.MyPlugin { *; }
-keepclassmembers class * { @org.bukkit.event.EventHandler <methods>; }
-keep class * implements org.bukkit.configuration.serialization.ConfigurationSerializable { *; }
-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

-printmapping build/obf/mapping.txt
-dontwarn

Add a -keep line for every package you reach by reflection. For more strength, run Skidfuscator after ProGuard: turn on string encryption first, test, then control flow, and test again. Exclude the class holding any store placeholder from string encryption.

Test the jar before you sell it

Never upload an obfuscated jar you haven't started. Five minutes:

  • Start a clean Paper server with only your plugin. No errors in the console.
  • Run every command, including tab completion.
  • Trigger your main events (join, break a block, open your GUI).
  • Restart the server: config and saved data load back.
  • Test on every server version listed on your store page.
  • Open the jar in a decompiler yourself and check it reads like noise.

If something fails, the error names the class. Keep that package unrenamed, or step down from Maximum to ProGuard, and run again.

Obfuscation is half the job

An obfuscated jar that anyone can run is still a jar anyone can share. The other half is one license key per buyer, checked on startup: a shared key then shows up on too many servers, and a leaked jar names its buyer. I wrote that up separately: How to add license keys to a Minecraft plugin in 10 minutes.

Licensing and obfuscation are both on SunLicense's free plan, with no cut of your sales. See how it works for Minecraft plugins.

๐Ÿ“ฐ Read the original article on Dev.to Security

Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes โ€” full credit and traffic to the original publisher.