
A friendly guide to four ways Kotlin reaches native power: Kotlin Native, JNI, JNA and the JDK Foreign Function and Memory API. What they are, pros and cons, where they shine and how to choose.
Think of native power as a party. Kotlin can show up through four different doors: Kotlin/Native (K/N), JNI, JNA and FFM. Same venue, different guest lists. This guide explains which door to use, when and why.
Good reads
- Kotlin Native overview
- Kotlin Native C interop guide
- JNI specification and tutorial (click on the JNI <x.x> Specification link)
- JNA project and usage examples
- Foreign Function and Memory API (aka Project Panama) JEP 442 overview, jextract tool
- Android NDK guides
Party Floorplan (30 second tour)
flowchart LR
A[Kotlin code]
KN["Kotlin Native<br/>(AOT native binary)"]
JNI["JNI<br/>(manual C/C++ glue)"]
JNA["JNA<br/>(no C glue, higher overhead)"]
FFM["FFM API<br/>(Java 21+, safe and fast)"]
R((Native code or OS APIs))
A -->|Door 1| KN
A -->|Door 2| JNI
A -->|Door 3| JNA
A -->|Door 4| FFM
KN --> R
JNI --> R
JNA --> R
FFM --> R
What changes by door? Mostly the runtime you stand in, call overhead, memory rules, tooling and portability.
Backstage Wiring: How calls reach Machine Code
flowchart TB
A[Kotlin code]
subgraph JVM path
A --> KJ[Kotlin on JVM]
KJ --> JNI[JNI]
KJ --> JNA[JNA]
KJ --> FFM[FFM API]
subgraph Extra setup on JVM
E1["C/C++ glue"]:::need
E2[JNA library and dispatcher]:::need
E3["JDK 21+"]:::need
end
JNI --> E1 --> N[Native library or OS API]
JNA --> E2 --> N
FFM --> E3 --> N
end
N --> MC[Machine code on CPU]
subgraph Native path
KN[Kotlin Native] --> CI[cinterop and link] --> BIN[Native binary]
end
BIN --> MC
classDef need fill:#e9d5ff,stroke:#a78bfa,color:#1f2937
House Rules: Quick picks
| Goal or constraint | Best bet | Why |
|---|---|---|
| iOS or no JVM allowed | Kotlin Native | AOT native binaries, direct C or Obj C or Swift interop, small runtime, fast startup |
| Max call performance on JVM | FFM or JNI | FFM gives JNI class speed with safer pure Java APIs on Java 21 plus. JNI is fast but verbose and unsafe |
| Ship fast with minimal glue | JNA | No C or C++ glue, just declare an interface. Accept higher per call overhead |
| Android NDK interop | JNI | First class on Android today, FFM is not available on Android runtimes |
| Quick CLI with small footprint | Kotlin Native | Single native binary, great startup, easy POSIX or Win32 access |
| Zero copy off heap buffers on JVM | FFM | MemorySegment and MemoryLayout, safe structured off heap data |
Remember: on desktop or server JVMs try FFM first if you can target Java 21 plus. On Android use JNI. For iOS or pure native apps use K/N. Reach for JNA when convenience beats speed.
The Guest List side by side
Door 1: Kotlin Native (K/N)
What it is Kotlin compiled ahead of time (AOT) with LLVM to a native binary or library, no JVM. Interops with C directly and on Apple platforms with Objective C and Swift.
Pros
- Runs where the JVM cannot, including iOS and some embedded targets
- Fast startup and small runtime, great for CLIs and daemons
- Direct access to POSIX or Win32, cinterop generates bindings
- Modern memory manager with ongoing performance gains
- Swift Export improves iOS developer experience
Cons
- Not a JVM artifact, so you cannot load it into a JVM and reuse JVM libraries
- Peak throughput can be below a hot JVM for complex long running workloads
- Longer build and link times and you need native toolchains per OS
Best iOS apps or frameworks, cross platform CLIs, low footprint services, direct OS or API integrations.
Avoid If you need JVM libraries and tooling or you rely on JVM JIT peak throughput.
Door 2: Java Native Interface (JNI)
What it is Low level bridge for Java or Kotlin on the JVM to call C or C++ and be called back.
Pros
- Very low call overhead for performance critical paths
- Full power to manipulate JVM objects from native code, including callbacks and throwing exceptions
- Works everywhere a JVM works, including Android with the Android NDK
Cons
- You write and maintain C or C++ glue per platform
- Easy to crash the VM, memory safety is on you
- Verbose APIs and debugging spans two worlds
Best Android NDK integration and hot loops where every nanosecond counts and legacy codebases needing deep JVM and native mixing.
Avoid If you want maintainability and safety and can use FFM instead.
Door 3: Java Native Access (JNA)
What it is A higher level library that uses JNI under the hood so you do not write C. You declare Java or Kotlin interfaces that mirror C APIs.
Pros
- Zero C glue, fastest way to just call the DLL or .so
- Portable, same code across Windows, Linux and macOS
- Good for tooling, admin utilities and light interop
Cons
- Higher per call overhead, often much slower than JNI in micro benchmarks
- Reflection and dynamic mapping make bugs harder to chase
- Needs bundling the native dispatcher on some platforms
Best Infrequent calls, prototypes, internal tools and non critical paths.
Avoid If you have tight loops or large data marshaling.
Door 4: Foreign Function and Memory API (FFM)
What it is Modern JDK API on Java 21 plus for native calls and safe off heap memory. Often paired with jextract to generate bindings.
Pros
- JNI class performance with pure Java code
- MemorySegment and #memorylayout for zero copy and bounds checked off heap data
- Easy upcall and downcall without manual JNI
- Maintained and optimized with the JDK
Cons
- Requires a modern JDK and is not on Android today
- New APIs to learn and evolving ergonomics
Best Desktop or server JVM apps needing high throughput native calls and structured off heap I or O with long term safe interop.
Avoid If you must support old JDKs or Android.
Dance Floor Showdown: JVM vs K/N, How the night plays out?
- Cover charge: JVM asks for a heavier entry price. You bring a full runtime with JIT and GC and rich staff like profilers and monitors. In return you get free refills later because the JIT learns your moves and pours faster as the night goes on
- Instant entry: K/N gets you in fast. One native binary and the music starts right away. No coat check line. Perfect when you want instant vibes or when the venue does not allow a JVM
- Direct bartender access: K/N lets you talk to the bartender directly through POSIX or Win32 or Apple APIs. Powerful, but you mix some drinks yourself like native headers and toolchains and a bit of memory care
- Full house service: JVM gives you staff for almost everything. A giant library shelf, battle tested GC, warm caches. Peak dancing speed arrives after warm up and you rarely touch raw bottles
- Two venues, not one: K/N does not run inside the JVM. K/N builds native binaries or libraries and a .klib cannot be loaded into the JVM. You can use the same native .so in both worlds: on the JVM through JNI or FFM or JNA and in K/N via cinterop (link at build time or load at runtime). The mechanisms differ, the idea is the same.
Bouncer List: Where each Ticket Works
| Tech or target | Windows | Linux | macOS Intel or ARM | Android | iOS |
|---|---|---|---|---|---|
| Kotlin Native | ✓ exe or so | ✓ | ✓ | ✗ not typical in app | ✓ |
| JNI | ✓ | ✓ | ✓ | ✓ NDK | ✗ |
| JNA | ✓ | ✓ | ✓ | ⚬ possible with effort | ✗ |
| FFM JDK | ✓ Java 21+ | ✓ Java 21+ | ✓ Java 21+ | ✗ | ✗ |
Legend: ✓ supported, ⚬ possible or not common, ✗ not applicable
Who Rocks Which Corner
- K/N: iOS, native CLIs, tight OS integrations, low latency boot
- JNI: Android, tight inner loops that run many times per second after warm up, deep JVM and native callbacks
- JNA: Quickest to integrate for developers and great for admin tools and light or shallow interop. Expect higher per call overhead and avoid hot loops
- FFM: Modern JVM apps, off heap and zero copy, long term safe performance
Pick Your Door: Quick Map
graph TD
Q{Where will it run?}
Q -->|iOS or no JVM| KN[Kotlin Native]
Q -->|Android| JNI
Q -->|Desktop or Server JVM| JDK[Java 21 plus?]
JDK -->|Yes| FFM
JDK -->|No| Choice[Need speed?]
Choice -->|Yes| JNI
Choice -->|No| JNA
Party Hacks and Tricky Spots
Kotlin Native
- Keep interop surfaces tight with header filters. Use memScoped and CValues helpers
- On Apple, prefer Swift exported APIs when embedding into Xcode
JNI
- Minimize crossings and batch work. Use GetPrimitiveArrayCritical carefully
- Test on each OS or ABI. CI cross compilation pays off
JNA
- Avoid hot loops. Prefer direct buffers for bulk data
- Enable JNA debug flags when resolving symbol issues
FFM
- Model data with MemoryLayout and reuse MethodHandles
- Think in scopes: who owns off heap memory and for how long
Glossary
Kotlin Native
A Kotlin compiler backend that produces native binaries and libraries. It runs without a JVM and interops with C and platform APIs. Often abbreviated K/N.
Java Native Interface
The low level bridge that lets Java or Kotlin on the JVM call C or C++ and be called back. Requires writing C glue code.
Java Native Access
A higher level library that lets Java or Kotlin declare interfaces for C libraries without writing C code. Uses JNI under the hood and trades speed for convenience.
Foreign Function and Memory API
A JDK API that provides native calls and safe off heap memory through classes like MemorySegment and MemoryLayout. Available on modern desktop and server JDKs.
Just in Time (JIT)
A runtime compiler in the JVM that turns hot bytecode into optimized machine code during execution.
Ahead of Time (AOT)
Compilation that produces machine code before running the program. Used by Kotlin Native and by other native image tools.
MemorySegment
An object that represents a bounded region of off heap memory in the FFM API. It enables safe zero copy access.
MemoryLayout
A description of the shape and alignment of native data structures used with the FFM API.
Upcall and Downcall
Downcall means Java or Kotlin code on the JVM calls a native function. Upcall means native code calls back into Java or Kotlin.
cinterop
The Kotlin Native tool that generates bindings from C headers to Kotlin stubs so Kotlin can call C functions and work with C types.
jextract
A tool from the JDK that reads C headers and generates Java bindings for use with the FFM API.
Android NDK
The native development kit for Android. It provides toolchains and headers to write C or C++ that works with Android through JNI.
Swift Export
A Kotlin Native feature that makes Kotlin APIs friendlier to call from Swift on Apple platforms.
LLVM
Low Level Virtual Machine, a compiler infrastructure project that provides a collection of modular and reusable compiler and toolchain technologies. Kotlin Native uses LLVM to compile Kotlin code into native machine code for various platforms.
Zero copy off heap buffers
Memory buffers allocated outside of JVM heap memory that can be accessed directly without copying data between JVM and native memory. This eliminates the overhead of data marshaling and improves performance for I/O operations.
memScoped
A Kotlin Native function that creates a scope for native memory allocation. Memory allocated within this scope is automatically freed when the scope exits, preventing memory leaks.
CValues
Kotlin Native helper classes for working with C primitive values and structures. They provide type-safe wrappers around native C data types.
GetPrimitiveArrayCritical
A JNI function that provides direct access to the raw array data without copying, but must be used carefully as it may temporarily disable garbage collection. Should be held for the shortest time possible.
Last Call
Four doors and one party. Choose by platform first, then by performance versus productivity. If you remember just one line: FFM on modern JVMs, JNI on Android, K/N for iOS or native binaries, JNA when you just need it working by lunch.