RJNI takes a built jar and produces a protected jar. It does not touch your build process beyond that one extra step. Run it after your normal package or assemble step, before you ship the artifact.
rjni protect --in build/libs/app.jar --out build/libs/app-protected.jar
The protected jar is a drop-in replacement: same entry point, same public behavior. It runs on an ordinary JVM. Nothing about how you deploy or launch it changes.
Every eligible method is translated to a Rust intermediate representation, compiled to a
native cdylib, and relinked into your class through JNI's
RegisterNatives. The method's bytecode is gone from the jar. A decompiler sees
a native method declaration with no body. On top of that, a chain of independent protection
passes runs: name obfuscation, string encryption, control-flow flattening, decompiler
crashers, anti-tamper checksums, and anti-debug detection. Each pass is self-contained, so a
class that opts out of nativization can still be renamed and have its strings encrypted.
Not everything can be safely translated or renamed. RJNI ships pluggable exclusion
profiles for the common cases: reflection by name (Class.forName),
ServiceLoader provider lookups, JPMS module-info descriptors, and
common proxy-framework internals (Spring AOP, CGLIB and ByteBuddy-style subclassing). A
class or method a profile excludes is left untouched, written through to the output jar
byte for byte.
If your project needs something the built-in profiles do not cover, add your own keep rules:
rjni protect --in app.jar --out app-protected.jar --keep "com.example.plugins.**"
Class file major versions 52 through 70 (JDK 8 through 26) are supported. Native library targets:
One shared native library is built per platform and architecture combination in your build matrix and bundled into the output jar as a resource. The correct one loads automatically at runtime based on the host it runs on.
Pass --trial to protect only a deterministic subset of eligible classes
instead of the whole jar: enough to see the pipeline working end to end on a real build
before fully committing.
rjni protect --in app.jar --out app-trial.jar --trial 0.2
Documented directly, not left implicit:
RegisterNatives is a
name-based API and there is no way around that without abandoning JNI entirely.