Make reverse-engineering
not worth it.

rymga Lock Master is *the* next-generation
Java bytecode obfuscator.

To pull your code apart, an attacker has two ways in: study it standing still, or watch it run. rymga Lock Master closes both. Your classes ship sealed, your logic is scrambled past reconstruction, every string is encrypted to the code that uses it, and the whole build turns hostile the moment someone attaches a debugger. What follows is the entire engine, part by part.

Your bytecode never ships as bytecode.

  • Classes encrypted and removed. ClassGuard encrypts every class with strong authenticated encryption and strips the originals from the archive. What ships is a set of opaque encrypted blobs, with no loadable bytecode on disk to disassemble.
  • Decrypted only in memory. A generated bootstrap class loader unseals each class on demand, at load time. The key is reconstructed at runtime and never stored verbatim in the artifact.
  • Survives modern runtimes. The bootstrap loader works in any Java application, and it's built for today's reflection-hardened runtimes where classloaders are locked down. Where those constraints break protectors that rely on re-parenting the loader, ours keeps unsealing classes cleanly.
media
One class becomes an encrypted blob, unsealed only in memory at load time.

Control flow, rewritten past recognition.

  • Rebuilt from the graph up. Every method's control flow is flattened and re-routed through a masked dispatcher, then padded with opaque predicates, bogus branches and dead-but-live code. It runs identically, and there's no clean Java that reproduces it.
  • Loops and exceptions turned into traps. Loops are given fake back-edges and made irreducible, and exception ranges are deliberately overlapped. A decompiler can't rebuild the structure from that, so it drops to raw jumps or bails out part-way through the method.
  • Constants become computations. Integers and other constants are replaced with mixed boolean-arithmetic that only resolves at runtime, so a reader never sees the value, only the expression that produces it.
  • Branches only the caller can settle. Some predicates are fed by values passed in from every call site, so whether a branch is live depends on who called. Read the method on its own and you can't prove it's dead; you'd have to analyze the whole program at once, which the common decompilers don't.
media
The same method's control-flow graph, before and after flattening.

Nothing legible left in the class file.

  • Every string encrypted on its own. No literal is left in the clear. Each one is encrypted separately and only rebuilt the first time it's actually needed, then cached, so there's no plaintext sitting there to grep for.
  • Keys tied to the calling class. Each string's key is derived from the class that uses it. Copy the string somewhere else and it decodes to garbage, so scraping literals out of the bytecode gets nothing.
  • Calls and field access hidden too. Method calls and field reads are rewired so the class file no longer records real names for them, leaving placeholders where the targets used to be. What reaches what stops being obvious.
  • Or moved out of the classes entirely. Optionally, every encrypted string is gathered into a single sealed file inside the jar, off the class files for good.
media
A literal decrypted on first use, keyed to the class that owns it.

Sealed at rest. Hostile while running.

  • Static analysis has nothing to chew on. With classes sealed and strings gone, disassembly and decompilation stall, so attackers fall back to running the code under observation. That path is closed too.
  • Debuggers are locked out. A runtime guard inspects the JVM launch configuration for debuggers, and optionally instrumentation agents. When either is present, the encrypted classes are never unsealed.
  • Live dumps denied by construction. Because the class keys only resolve in a clean process, dumping the decrypted classes out of a debugged or agent-attached JVM does not work.
  • What runs is mostly decoys. The obfuscated flow that does execute is dominated by dead paths, so even a successful capture is buried in noise.
media
A debugger attaches — and the encrypted classes never unseal.

Every build is traceable to a licensee.

  • A keyed, recoverable watermark. Each build carries an invisible identifier tying it to the exact license it was issued under.
  • Fragmented and redundant. The mark is split, distributed across classes and checksummed, so it survives re-jarring, partial deletion, or re-obfuscation of a subset.
  • Attributable, never exposed. Reading it back requires a vendor secret that never ships in the build, so a leak names the licensee without revealing the scheme.
media
The licensee mark, split and checksummed across the archive.

Hardened, and verified before it ships.

  • Re-verified against the JVM's own rules. Every transformed class is checked by a from-scratch bytecode verifier that reproduces load-time verification over a custom class hierarchy. Anything that would throw a VerifyError is caught before delivery, never on your server.
  • Concurrency-aware. Timing- and thread-sensitive code, like wait/notify and thread startup, is hardened in a timing-safe way rather than blindly, so multithreaded behavior stays identical, right where careless obfuscators introduce races.
  • Deterministic and incremental. Builds are reproducible and support incremental name mapping, so obfuscated public APIs stay stable across releases, which plugins and libraries shipping updates depend on.
  • Precise keep rules. Glob-based rules exempt public entry points and reflectively accessed members from every transform.
media
Every transformed class re-checked against the JVM's own rules.

Your code should cost more
to steal than to build.
That's the whole job.

There's no honest way to make compiled software impossible to reverse. What we can do is stack the odds: seal what ships, scramble what's left, and make the whole thing fight back the moment anyone tries to read it, at rest or while it runs. And every build is checked before it reaches you, so none of that protection ever costs you a crash. We push cracking it past the point of being worth it, and we'll show you exactly how.

See it on real code.

One class, three ways: your source, a light pass, and everything turned up.

01public class HelloWorld {02 03    public static void main(String[] args) {04        String name = args.length > 0 ? args[0] : "world";05        System.out.println(greet(name));06        System.out.println("Sum of 1.." + name.length() + " = " + sum(name.length()));07    }08 09    static String greet(String name) {10        if (name == null || name.isEmpty()) {11            return "Hello, stranger!";12        }13        return "Hello, " + name + "! Welcome to rymga.";14    }15 16    static int sum(int n) {17        int total = 0;18        for (int i = 1; i <= n; i++) {19            total += i;20        }21        return total;22    }23}

Exactly as you wrote it.

01/*02 * Decompiled with CFR.03 */04 05public final class 公是 {06    private static /* synthetic */ String[] 面面;07    private static /* synthetic */ int 面只;08	// ...09 10    static {11        面只 = 8848 + 5616 ^ 0xD18D4188 ^ 公是.class.getName().hashCode();12        面面 = new String[3];13        公是.面面[0] = 公是.去231af2d6("着公和去没么为日后只同方了里者大");14 15        // ...16    }17 18 19    private static /* synthetic */ String 面从(int n) {20        String string = 面公[n];21        if (string != null) {22            return string;23        }24        String string2 = 公是.会见();25        int n2 = 面当 ^ string2.hashCode() ^ 0xC4E8;26        char[] cArray = 面没[n].toCharArray();27        int n3 = cArray.length;28        try {29            int n4;30            byte[] byArray = new byte[16];31            for (n4 = 0; n4 < 16; ++n4) {32                byArray[n4] = (byte)(n2 >> n4 % 4 * 8 ^ n4);33            }34            SecretKeySpec secretKeySpec = new SecretKeySpec(byArray, 公是.去231af2d6("的道当"));35            Cipher cipher = Cipher.getInstance(公是.去231af2d6("的道当从种事与也前无只看从小起当大只为心"));36            cipher.init(2, secretKeySpec);37            byte[] byArray2 = new byte[n3];38            for (n4 = 0; n4 < n3; ++n4) {39                byArray2[n4] = (byte)(cArray[n4] & 0xFF);40            }41            byte[] byArray3 = cipher.doFinal(byArray2);42            int n5 = byArray3.length / 2;43            char[] cArray2 = new char[n5];44            for (n4 = 0; n4 < n5; ++n4) {45                cArray2[n4] = (char)((byArray3[n4 * 2] & 0xFF) << 8 | byArray3[n4 * 2 + 1] & 0xFF);46            }47            cArray = cArray2;48            n3 = n5;49        }50        catch (Exception exception) {51            throw new RuntimeException(exception);52        }53        公是.面公[n] = new String(cArray);54        公是.面没[n] = null;55        Arrays.fill(cArray, '的');56        return 面公[n];57    }58 59    public static /* synthetic */ CallSite 面多(MethodHandles.Lookup lookup, String string, MethodType methodType, int n) {60        String string2 = 公是.面从(n);61        return new ConstantCallSite(MethodHandles.constant(String.class, string2));62    }63 64    private static /* synthetic */ String 去231af2d6(String string) {65        char[] cArray = string.toCharArray();66        for (int i = 0; i < cArray.length; ++i) {67            cArray[i] = (char)(cArray[i] ^ 31419 + i * 227);68        }69        return new String(cArray);70    }71    72    // ...73}

The same class, a light pass. The strings are already gone.

01/*02 * Decompiled with CFR.03 */04 05public final class 公是 {06    private static /* synthetic */ String[] 面面;07    private static /* synthetic */ String[] 面又;08    private static /* synthetic */ int 面只;09    // ...10 11    static {12        面只 = 29613 + 2488 ^ 0x28761DD5 ^ 公是.class.getName().hashCode();13        面面 = new String[12];14        公是.面面[0] = 公是.去12400bc1("法前起以年进的其都法定他用其其");15        公是.面面[1] = 公是.去12400bc1("此心同这能与是");16        // ...17    }18 19    /*20     * Exception decompiling21     */22    public static /* synthetic */ int 又(int var0) {23        /*24         * This method has failed to decompile.  When submitting a bug report, please provide this stack trace, and (if you hold appropriate legal rights) the relevant class file.25         * 26         * org.benf.cfr.reader.util.ConfusedCFRException: Tried to end blocks [9[TRYBLOCK]], but top level block is 18[UNCONDITIONALDOLOOP]27         *     at org.benf.cfr.reader.bytecode.analysis.opgraph.Op04StructuredStatement.processEndingBlocks(Op04StructuredStatement.java:435)28         *     at org.benf.cfr.reader.bytecode.analysis.opgraph.Op04StructuredStatement.buildNestedBlocks(Op04StructuredStatement.java:484)29         *     at org.benf.cfr.reader.bytecode.analysis.opgraph.Op03SimpleStatement.createInitialStructuredBlock(Op03SimpleStatement.java:736)30         *     at org.benf.cfr.reader.bytecode.CodeAnalyser.getAnalysisInner(CodeAnalyser.java:850)31         *     at org.benf.cfr.reader.bytecode.CodeAnalyser.getAnalysisOrWrapFail(CodeAnalyser.java:278)32         *     at org.benf.cfr.reader.bytecode.CodeAnalyser.getAnalysis(CodeAnalyser.java:201)33         *     at org.benf.cfr.reader.entities.attributes.AttributeCode.analyse(AttributeCode.java:94)34         *     at org.benf.cfr.reader.entities.Method.analyse(Method.java:531)35         *     at org.benf.cfr.reader.entities.ClassFile.analyseMid(ClassFile.java:1050)36         *     at org.benf.cfr.reader.entities.ClassFile.analyseTop(ClassFile.java:942)37         *     at org.benf.cfr.reader.Driver.doJarVersionTypes(Driver.java:257)38         *     at org.benf.cfr.reader.Driver.doJar(Driver.java:139)39         *     at org.benf.cfr.reader.CfrDriverImpl.analyse(CfrDriverImpl.java:76)40         *     at org.benf.cfr.reader.Main.main(Main.java:54)41         */42        throw new IllegalStateException("Decompilation failed");43    }44 45    static {46        三0 = (int)System.nanoTime();47    }48}

The decompiler couldn't reconstruct the code at all.

Simple plans.
Access by approval.

Make your code a bad target