Four Doors, One Party - K/N, JNI, JNA & FFM Four Doors, One Party - K/N, JNI, JNA & FFM

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.

Listen to this article

AI-generated narration

0:00 / -:--

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


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 constraintBest betWhy
iOS or no JVM allowedKotlin NativeAOT native binaries, direct C or Obj C or Swift interop, small runtime, fast startup
Max call performance on JVMFFM or JNIFFM gives JNI class speed with safer pure Java APIs on Java 21 plus. JNI is fast but verbose and unsafe
Ship fast with minimal glueJNANo C or C++ glue, just declare an interface. Accept higher per call overhead
Android NDK interopJNIFirst class on Android today, FFM is not available on Android runtimes
Quick CLI with small footprintKotlin NativeSingle native binary, great startup, easy POSIX or Win32 access
Zero copy off heap buffers on JVMFFMMemorySegment 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 targetWindowsLinuxmacOS Intel or ARMAndroidiOS
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.