Advanced Protection
Go beyond basic verification. Implement LicenseGuard, dispersed checks, and ProGuard obfuscation to make your plugin nearly impossible to crack.
LicenseGuard Pattern
Wrap the SDK in a dedicated utility class that handles verification, heartbeat, and enforcement. This class should NOT implement Listener or CommandExecutor so ProGuard can fully obfuscate it.
package com.example.myplugin.util;
import ch.irixiagroup.ilicence.ILicence;
import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;
import java.time.Duration;
public class LicenseGuard {
private final JavaPlugin plugin;
private ILicence licence;
public LicenseGuard(JavaPlugin plugin, String key, String pluginId) {
this.plugin = plugin;
this.licence = ILicence.builder()
.plugin(plugin)
.licenseKey(key != null ? key : "")
.pluginId(pluginId)
.heartbeat(Duration.ofMinutes(30))
.gracePeriod(Duration.ofHours(24))
.onStatusChange((wasValid, isNowValid) -> {
if (!isNowValid) {
Bukkit.getScheduler().runTask(plugin, this::shutdown);
}
})
.verify();
}
public boolean check() {
return licence != null && licence.isValid();
}
public boolean enforce() {
if (!check()) { shutdown(); return false; }
return true;
}
private void shutdown() {
plugin.getLogger().severe("License validation failed. Disabling plugin.");
Bukkit.getPluginManager().disablePlugin(plugin);
}
public void stop() {
if (licence != null) licence.stop();
}
}Why a wrapper?
Separating license logic into its own class makes it harder to find and remove. ProGuard will rename this class to something like a.class, hiding its purpose entirely.
Main Class Integration
Initialize the LicenseGuard in onEnable() before any other manager. Read the license key from config.yml — never hardcode it.
private LicenseGuard licenseGuard;
@Override
public void onEnable() {
saveDefaultConfig();
String licenseKey = getConfig().getString("license-key", "");
if (licenseKey.isEmpty()) {
getLogger().severe("No license key! Set 'license-key' in config.yml");
getServer().shutdown();
return;
}
licenseGuard = new LicenseGuard(this, licenseKey, "my-plugin-slug");
// ... rest of your plugin init
}
@Override
public void onDisable() {
if (licenseGuard != null) licenseGuard.stop();
}Dispersed License Checks
Don't rely on a single verification at startup. Add enforce() calls at critical points in your game logic. Even if someone removes the startup check, the plugin will still shut down when it hits a dispersed check.
// In your most critical method (game start, match begin, etc.)
public boolean startGame(Player player) {
if (!licenseGuard.enforce()) return false;
// ... game logic
return true;
}Obfuscation with ProGuard
ProGuard renames your classes, methods, and fields to meaningless names like a, b, c. This makes it extremely hard for someone to find and remove your license checks by decompiling the JAR.
Maven Setup
Add the ProGuard Maven plugin to your pom.xml after the maven-shade-plugin. It obfuscates the already-shaded JAR so all dependencies are included.
<plugin>
<groupId>com.github.wvengen</groupId>
<artifactId>proguard-maven-plugin</artifactId>
<version>2.6.1</version>
<executions>
<execution>
<phase>package</phase>
<goals><goal>proguard</goal></goals>
</execution>
</executions>
<configuration>
<proguardVersion>7.6.1</proguardVersion>
<injar>${project.build.finalName}.jar</injar>
<outjar>${project.build.finalName}.jar</outjar>
<obfuscate>true</obfuscate>
<options>
<!-- Keep your main class name -->
<option>-keep public class com.example.MyPlugin {
public void onEnable();
public void onDisable();
}</option>
<!-- Keep Bukkit event handlers + commands -->
<option>-keep class * implements org.bukkit.event.Listener { *; }</option>
<option>-keep class * implements org.bukkit.command.CommandExecutor { *; }</option>
<option>-keep class * implements org.bukkit.command.TabCompleter { *; }</option>
<!-- Keep enums + ILicence SDK -->
<option>-keepclassmembers enum * { *; }</option>
<option>-keep class ch.irixiagroup.ilicence.** { *; }</option>
<!-- Only obfuscate, don't shrink or optimize -->
<option>-dontshrink</option>
<option>-dontoptimize</option>
<option>-dontwarn **</option>
</options>
<libs>
<lib>${java.home}/jmods/java.base.jmod</lib>
<lib>${java.home}/jmods/java.logging.jmod</lib>
</libs>
</configuration>
<dependencies>
<dependency>
<groupId>com.guardsquare</groupId>
<artifactId>proguard-base</artifactId>
<version>7.6.1</version>
</dependency>
<dependency>
<groupId>com.guardsquare</groupId>
<artifactId>proguard-core</artifactId>
<version>9.1.7</version>
</dependency>
</dependencies>
</plugin>Important Rules
ProGuard must run AFTER the shade plugin. Keep your main class, Bukkit listeners, command executors, and the ILicence SDK un-obfuscated. Everything else (including LicenseGuard) gets obfuscated.
What Gets Obfuscated
| Class Type | Obfuscated? | Why |
|---|---|---|
| Main plugin class | Name kept | Bukkit needs to find it via plugin.yml |
| Event listeners | Name kept | Bukkit reflects on @EventHandler methods |
| Command executors | Name kept | Bukkit reflects on onCommand() |
| LicenseGuard | Fully obfuscated | Not a Listener/Executor — hidden by design |
| Utility classes | Fully obfuscated | Internal implementation details |
| ILicence SDK | Kept as-is | Shaded dependency, needs reflection |
| Enums | Values kept | Java enum serialization requires it |
Verify Obfuscation
After building, run jar tf target/YourPlugin.jar | grep -E "Guard|Util" and check that your utility classes appear as single letters like a.class. If you see the original names, your ProGuard config needs adjustment.
Best Practices
Never hardcode license keys
Always read from config.yml. Ship with an empty default.
Use heartbeat + grace period
30-minute heartbeat with 24-hour grace. Catches revocations without false positives.
Disperse your checks
Add enforce() in 3-5 critical methods across different classes.
Obfuscate with ProGuard
Makes license removal nearly impossible by hiding class and method names.
Keep LicenseGuard clean
Don't implement Listener or CommandExecutor on it. Let ProGuard hide it completely.
Handle onDisable() safely
Call licenseGuard.stop() first, wrap cleanup in try-catch for early shutdown scenarios.
How It Stops Cracking
Attack: Decompile JAR and remove verify() call
Defense: Dispersed checks in critical methods. License check at game start, match begin, command execution.
Attack: Patch ILicence.verify() to always return valid
Defense: ProGuard hides the class name (becomes a.class). Cracker can't find it.
Attack: Modify the plugin's config.yml
Defense: Unnecessary. License verified against API, not config file.
Attack: Intercept network traffic and fake API response
Defense: HTTPS TLS 1.3. Responses signed server-side (future: JWT).
Attack: Wait 7 days for license to expire on server records
Defense: Heartbeat checks every 30 minutes. Plugin disables if server stops checking in.