Browse documentation
Documentation / How it works

How it works

RJNI runs six stages over your JAR, in this order, every time. The order is fixed, so behaviour is predictable and reproducible.

  1. 01

    Input and configuration

    You supply a built JAR, an optional trial fraction, optional class-level keep patterns, and an optional watermark string. See Configuration.

  2. 02

    Parsing and eligibility scan

    Every class is parsed with ASM and checked against the registered exclusion profiles (see Exclusions) and an interface check, an interface method can never legally carry ACC_NATIVE, so whole interfaces are excluded from nativization eligibility here (though not from renaming, see Class name obfuscation). Every LDC string constant and static final String value across the whole JAR is also collected here, so later passes know what's unsafe to rename.

  3. 03

    Bytecode-level protection passes

    On the surviving, eligible classes: class renaming, string encryption, method/field renaming, and control-flow flattening each run as an independent pass over the same class set, in that order. String encryption runs before nativization's own eligibility scan, so a nativized method's compiled Rust code never embeds a plaintext string literal either.

  4. 04

    Per-method translation

    Every remaining method body (skipping <init>/<clinit>, already-native, and abstract methods) is exported to an intermediate representation and checked for translatability. A method that throws here is excluded and recorded with a reason. It does not stop the run.

  5. 05

    Native compilation and JNI relinking

    Every method that exported successfully is compiled, in one batch, into a single shared native library for the whole JAR, not one library per method. Each method that compiled is flipped to native via bytecode rewriting and bound through RegisterNatives, resolved against exactly the calling class regardless of which class loader defined it.

  6. 06

    Finalisation and packaging

    In this exact order: decompiler-resistant wrapping of whatever's left as ordinary bytecode; anti-tamper and anti-debug injection into every protected class's static initialiser; resource call-site rewriting for matched files; an optional watermark embedded; RJNI's own bundled runtime classes renamed per build; the native library obfuscated and bundled under a randomised path; anti-tamper checksums computed last, over the exact bytes that ship. The result is written out as a single JAR.

A method that can't be safely translated, and a bytecode shape RJNI doesn't yet handle, are both excluded with a recorded reason. Neither is mistranslated or left to crash at runtime. See Known limitations for the current list.