Skip to main content

03. Prior-art JVM bridges

MEP-70 is not the first attempt to bridge native code and the JVM. This note surveys ten prior-art systems, cataloguing what each gets right, what boilerplate it requires, and what the Kotlin bridge borrows.

JNI (Java Native Interface)

What it is: The official Java standard for calling C code from Java and vice versa. Every JVM ships with jni.h and supports System.loadLibrary. Functions are identified by mangled names (Java_com_example_MyClass_myMethod).

What it gets right: Zero dependencies beyond the JDK; works everywhere; battle-tested since Java 1.1.

What it requires: For each function you want to call, you write: (1) a native declaration in a Java class, (2) a C implementation with the mangled signature, (3) a javah/javac -h invocation to generate the header, (4) System.loadLibrary call in a static initialiser, (5) error handling for UnsatisfiedLinkError. For 100 functions, that is 500+ lines of glue spread across three languages.

What MEP-70 borrows: The JNI ABI is the wire protocol between GraalVM Native Image and the Mochi C layer. The bridge generates all JNI glue automatically; the user sees none of it.

JNA (Java Native Access)

What it is: A library that uses Java reflection and libffi to call native shared libraries without writing C. The user declares a Java interface whose method names match the C function names.

What it gets right: Eliminates C code for the Java-calling-native direction. No compilation step for the Java side.

What it requires: Still requires the user to (1) know the C function signatures, (2) map C types to JNA types manually (Pointer, NativeLong, Structure), (3) handle lifecycle (library load, memory management). Not useful for the native-calling-JVM direction MEP-70 needs.

What MEP-70 borrows: The type-mapping annotation pattern (JNA's @Structure.FieldOrder and Structure subclassing for C structs) inspired the bridge's data-class accessor pattern.

JNR-FFI

What it is: A higher-level native FFI library that underlies JRuby's native interop. Uses ASM bytecode generation to create efficient native call stubs at runtime.

What it gets right: More type-safe than JNA; generates actual bytecode rather than reflection-based dispatch; supports most C types including off_t, size_t, and calling conventions.

What it requires: Same declarative interface as JNA from the user's perspective. Not applicable to the native-calling-JVM direction.

What MEP-70 borrows: JNR's approach of treating the native interface as a first-class typed declaration (rather than raw Pointer handles) validates the bridge's strategy of emitting strongly-typed extern type declarations in the Mochi shim.

Project Panama (java.lang.foreign, Java 22+)

What it is: The official Java standard replacement for JNI/JNA/JNR. Provides MemorySegment, MemoryLayout, FunctionDescriptor, and MethodHandle-based native call bindings. Includes jextract tool to auto-generate Java bindings from C headers.

What it gets right: Safe memory management (off-heap segments with explicit lifetime), typed function descriptors (no more raw Pointer), jextract eliminates manual binding for C headers.

What it requires: jextract consumes C headers, not Kotlin/JVM metadata. For the JVM-to-native direction, Panama provides Linker.nativeLinker().downcallHandle(...) which is cleaner than JNI but still requires per-function FunctionDescriptor declarations. For the native-to-JVM direction (MEP-70's case), Panama does not provide a solution; jextract only goes C→Java.

What MEP-70 borrows: jextract's model of auto-generating Java bindings from a machine-readable interface description (C headers for Panama, @kotlin.Metadata for MEP-70) is the direct inspiration for the bridge's shim synthesis pipeline.

djinni (Dropbox)

What it is: A cross-platform interface generator for sharing Kotlin/Java and Objective-C/Swift code. Given an IDL file, djinni generates JNI glue for Java/Kotlin and ObjC wrapper code for Apple platforms.

What it gets right: Eliminates hand-written JNI; generates type-safe wrappers for both platforms from a single IDL; handles lifecycle (object ownership, reference counting).

What it requires: The user writes a djinni IDL file for every interface they want to bridge. This is one-time boilerplate, but it is boilerplate nonetheless. There is no IDL for the entire Maven Central catalog.

What MEP-70 borrows: djinni's approach of synthesising both sides of the bridge (the Java/Kotlin side and the native side) from a single machine-readable description is the architectural template MEP-70 follows. MEP-70 replaces the hand-written IDL with auto-generated IDL derived from @kotlin.Metadata.

SWIG (Simplified Wrapper and Interface Generator) for Java

What it is: A code generator that creates JNI wrappers from C/C++ header files, producing both C wrapper code and Java proxy classes.

What it gets right: Mature, widely used, handles complex C++ types.

What it requires: SWIG goes C++→Java, not Java→native. It is not applicable to MEP-70's direction. Mentioned for completeness because MEP-70 is the inverse: it takes the JVM API surface and emits a native wrapper.

GraalVM Polyglot API

What it is: GraalVM's Truffle-based polyglot embedding: a host language (Java, Python, etc.) can create a Context, evaluate guest language code, and exchange values via the polyglot value API.

What it gets right: Zero-cost interop between Truffle languages running in the same JVM process; works for any Truffle-hosted language.

What it requires: The host and guest must both run inside a GraalVM JVM. For MEP-70, the Mochi runtime is a native binary compiled by the Go toolchain; it does not run inside a JVM. The Polyglot API is inapplicable.

What MEP-70 borrows: GraalVM's native-image --shared flag, which produces a libwrap.so with a JNI-compatible C ABI and the graal_create_isolate lifecycle, is the mechanism MEP-70's wrapper compiler uses. This is GraalVM technology but not the Polyglot API.

Kotlin/Native C-interop

What it is: Kotlin/Native's mechanism for consuming C libraries. Given a .def file pointing to C headers, cinterop generates Kotlin wrappers that the Kotlin/Native compiler can use.

What it gets right: First-class Kotlin experience for C library consumption; handles memory management via memScoped and cValuesOf; supports structs, unions, and function pointers.

What it requires: The .def file is hand-written. The resulting Kotlin code only works in a Kotlin/Native context. For consuming JVM Kotlin libraries (the reverse direction), there is no official mechanism.

What MEP-70 borrows: The .def file model (a machine-readable description of a C library that generates typed wrappers) is analogous to MEP-70's shim.mochi (a machine-readable description of a Kotlin library that generates typed extern declarations). The key difference: MEP-70 synthesises the .def-equivalent from @kotlin.Metadata automatically.

j2objc (Google)

What it is: A transpiler that converts Java source to Objective-C, used by Google to share Java library code with iOS apps.

What it gets right: Produces idiomatic Objective-C from idiomatic Java; handles the Java standard library by providing Objective-C equivalents.

What it requires: Source-to-source translation; requires the Java source code (not just the JAR); generates a large amount of Objective-C that must be compiled into the iOS app.

What MEP-70 borrows: j2objc demonstrates that the JVM class model can be faithfully represented in a non-JVM type system, validating the bridge's type-mapping approach. j2objc's handling of Java generics (type erasure at the Objective-C boundary) is directly analogous to MEP-70's monomorphise table.

Py4J

What it is: A library that allows Python programs to call Java objects running in a JVM via a socket-based gateway.

What it gets right: Zero compilation step; any Java object is accessible from Python; bidirectional; works with arbitrary JVM versions.

What it requires: The JVM must be running as a server process; all calls go through a socket with JSON serialisation; latency is prohibitive for performance-sensitive code (microseconds → milliseconds per call).

What MEP-70 borrows: Py4J's model of dynamic proxy objects (Python objects that represent Java objects and dispatch method calls at runtime) is the highest-level abstraction in the space. MEP-70 rejects this model for performance reasons but its API surface inspection (Py4J's gateway.entry_point.getClass().getMethods()) illustrates the metadata-first approach.

Go-Java bridges (gojava, gobind)

What they are: gojava is an early experiment in embedding a JVM inside a Go binary. gobind (part of the Go mobile toolchain) generates Go/Java/ObjC bindings for mobile apps.

What they get right: gobind demonstrates that Go can interop with Java on Android via JNI, which validates the Go-JNI connection MEP-70's wrapper uses.

What they require: gobind requires the user to annotate Go functions with //go:generate gobind directives and is tightly coupled to the Android/iOS mobile deployment model. The resulting Go API surface is limited to types gobind understands (primitives, strings, slices, and error).

What MEP-70 borrows: The fundamental feasibility validation: Go can call JNI functions, and JNI can reach into GraalVM Native Image. The bridge's Go-layer code (package3/kotlin/wrapper/jni_caller.go) uses the same technique gobind uses for Android, applied to GraalVM-produced shared libraries.

Summary table

SystemDirectionBoilerplate requiredMEP-70 borrows
JNIbothC + Java per functionWire ABI
JNAnative→JVMJava interface per libraryType mapping model
JNR-FFInative→JVMJava interface per libraryTyped declarations
Panama/jextractnative→JVMauto from C headersAuto-bind from metadata
djinnibothIDL file per interfaceSynthesise both sides
SWIG/JavaC++→JavaC++ headers (wrong direction)N/A
GraalVM Polyglotin-JVM onlyNone if in JVM--shared flag
Kotlin/Native cinteropC→Kotlin/Native.def file per library.def-equivalent auto-generation
j2objcJava→ObjCJava source requiredGeneric erasure strategy
Py4JJVM↔PythonSocket gatewayDynamic proxy concept
gobindGo↔Java (Android)//go:generate directivesGo-JNI feasibility

No prior system provides automatic, no-boilerplate, bidirectional interop between a native compiled language and arbitrary Maven Central Kotlin artifacts from metadata alone. MEP-70 is new work.

Cross-references