Limitations
What Nativify can and can't protect, and what to expect at runtime.
Supported platforms
| OS | Architectures |
|---|---|
| Windows | x86_64, aarch64 |
| Linux | x86_64, aarch64, arm, x86 |
| Android | aarch64, x86_64, arm |
| macOS | x86_64, aarch64 |
Building Windows or macOS libraries from a different host OS requires the matching cross-toolchain.
Language & bytecode support
Supported
- Most of the modern JVM instruction set
- Exception handling
- Kotlin and other JVM languages
- Java 8+ at runtime (build with Java 21 recommended)
Partially supported
INVOKEDYNAMIC— the method is protected, but the dynamic call is extracted to a separate method.- Lambdas — supported via the same extraction.
MULTIANEWARRAY(multi-dimensional array creation) — extracted to a separate method.- Floating-point edge cases — results for NaN and division-by-zero may not exactly match the JVM specification.
Not supported
- Get-and-modify operations on
double[]/long[]elements (doubleArray[0]++,longArray[0] += 2.0). - Abstract methods, constructors (
<init>), and static initializers (<clinit>) cannot be selected for protection.
A method that can't be protected is simply left as normal bytecode.
Performance
Calls between native code and the JVM cross the JNI boundary, which has overhead. Purely computational work (arithmetic, branches, loops) runs natively with no such cost, but every field access, method call, or object operation that goes back into the JVM pays it.
Guidance: protect hot, sensitive methods — license checks, key algorithms, anti-tamper logic — rather than your whole application. See Benchmarks for measurements.
Long-running loops and memory
References created inside a protected method are released when the method returns. A method that loops "forever" (for example, a server accept-loop) can therefore accumulate references. If you protect such a method, move the loop body into a separate protected method so its references are released each iteration:
@Nativify
void serve(ServerSocket server) throws Exception {
while (true) {
handle(server); // references released when handle() returns
}
}
@Nativify
void handle(ServerSocket server) throws Exception {
Socket sock = server.accept();
// ...
}
Mutually exclusive features
native-unsafe and native-llvm-direct-call cannot be enabled together — the
compiler rejects the combination. Pick one. See
Configuration.