Skip to content

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.

See also