How do you call a native C function from Java with the Linker (downcall), and what is an upcall?
answer
- Downcall = Java→C; Upcall = C→Java callback
- Linker.nativeLinker() → SymbolLookup (default / libraryLookup) → address
- FunctionDescriptor maps C types to layouts (ADDRESS for pointers)
- downcallHandle → MethodHandle.invoke; pass MemorySegments as pointers
- upcallStub(handle, desc, arena) → a native function pointer (MemorySegment)
basics
~20 sA downcall is Java calling into C: you get the Linker, look up the function's address, describe its signature with a FunctionDescriptor, and get a MethodHandle you can invoke. An upcall is the reverse — you wrap a Java method as a function pointer so C can call back into Java.
solid answer
~50 sTo call C from Java (a downcall): get Linker.nativeLinker(), find the function's address with a SymbolLookup (e.g. the default lookup, or SymbolLookup.libraryLookup for your own library), and describe its C signature with a FunctionDescriptor that maps C types to ValueLayouts (return layout plus argument layouts; ADDRESS for pointers). linker.downcallHandle(address, descriptor) returns a MethodHandle you invoke with Java values, passing MemorySegments where the C function wants pointers. An upcall is the reverse direction: many C APIs take a callback function pointer, so you bind a Java MethodHandle to a FunctionDescriptor with linker.upcallStub(handle, descriptor, arena), which returns a MemorySegment that is a native function pointer C can call — invoking your Java code. The arena governs the stub's lifetime. You can also pass Linker.Option values (e.g. isTrivial, or capturing errno) to tune the call. This replaces JNI's handwritten glue entirely.
go deeper
Knows at a high level that Java can call C through the Linker by looking up a function and getting something it can invoke.
Can write a basic downcall: get the linker, find a symbol, build a FunctionDescriptor, invoke the MethodHandle with a MemorySegment.
Explains both directions (downcall/upcall), SymbolLookup variants, descriptor/layout mapping including ADDRESS, upcall-stub lifetime, and Linker.Options like isTrivial/errno capture.
Reasons about ABI correctness, the cost of boundary transitions, safe lifetime management of upcall stubs and loaded libraries, and when to generate bindings (jextract) vs hand-write them across platforms.
## The goal: cross the Java/native boundary in both directions Native interop has two directions. **Downcall** = Java code calls a C function. **Upcall** = C code calls back into Java (because the C API expects a *callback function pointer*). The FFM API handles both through the **`Linker`**. ## Step 1 — get the Linker ```java Linker linker = Linker.nativeLinker(); ``` `Linker.nativeLinker()` returns the linker for the **native ABI** (Application Binary Interface — the platform's calling convention for how arguments are passed in registers/stack). You normally don't think about the ABI; the linker implements it for you. ## Step 2 — find the function's address (SymbolLookup) A C function lives at an **address** in a loaded library. A **`SymbolLookup`** finds that address by name: - `linker.defaultLookup()` — symbols already available to the process (standard C library, etc.). - `SymbolLookup.libraryLookup("mylib", arena)` — load *your* shared library and look up its symbols; the `arena` controls how long the library stays loaded. ```java MemorySegment addr = linker.defaultLookup().find("strlen").orElseThrow(); ``` The address comes back as a `MemorySegment` (a pointer). ## Step 3 — describe the signature (FunctionDescriptor) C is not self-describing, so you must tell the linker the function's **signature**. A **`FunctionDescriptor`** maps C types to FFM **layouts**: - `FunctionDescriptor.of(returnLayout, argLayouts...)` for a function with a return value. - `FunctionDescriptor.ofVoid(argLayouts...)` for `void` functions. - Pointers map to `ValueLayout.ADDRESS`; `int`→`JAVA_INT`; `long`→`JAVA_LONG`; etc. For `size_t strlen(const char*)`: ```java FunctionDescriptor desc = FunctionDescriptor.of( ValueLayout.JAVA_LONG, // returns size_t (a long) ValueLayout.ADDRESS); // takes a char* (a pointer) ``` ## Step 4 — get a MethodHandle and invoke (the downcall) ```java MethodHandle strlen = linker.downcallHandle(addr, desc); try (Arena arena = Arena.ofConfined()) { MemorySegment cString = arena.allocateUtf8String("hello"); long len = (long) strlen.invoke(cString); // -> 5 } ``` A **`MethodHandle`** is a directly-invokable, typed reference to executable code. Here it *is* the C function: you call `.invoke(...)` with Java values, passing `MemorySegment`s where C wants pointers. No handwritten C glue, no separate compile step. ## Upcalls — letting C call Java Many C APIs take a **callback**: e.g. `qsort` needs a comparison function pointer. To supply one from Java you create an **upcall stub**: 1. Write a Java method matching the callback's signature and obtain a `MethodHandle` to it (via `MethodHandles.lookup().findStatic(...)`). 2. Describe the callback signature with a `FunctionDescriptor`. 3. `linker.upcallStub(handle, descriptor, arena)` returns a **`MemorySegment`** — a *native function pointer* — that C can call. Invoking it runs your Java method. ```java MethodHandle cmpHandle = MethodHandles.lookup().findStatic( MyClass.class, "compare", MethodType.methodType(int.class, MemorySegment.class, MemorySegment.class)); FunctionDescriptor cmpDesc = FunctionDescriptor.of( ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.ADDRESS); MemorySegment cmpPtr = linker.upcallStub(cmpHandle, cmpDesc, arena); // pass cmpPtr to a C function that expects a comparator pointer ``` The `arena` controls the **stub's lifetime**: when it closes, the function pointer becomes invalid, so don't let C hold it past that. ## Tuning the call: Linker.Option `downcallHandle`/`upcallStub` accept `Linker.Option` values, e.g.: - **`Linker.Option.isTrivial()`** — promise the call is very short/non-blocking so the JVM can use a cheaper transition (less overhead, but misuse can hurt the JVM). - **`captureCallState("errno")`** — capture the native `errno` (the C error variable) right after the call, since the JVM might otherwise clobber it. ## How this compares to JNI With **JNI** you'd write and compile a C shim per function and per platform, juggling `jstring`/`jint` conversions and manual reference management — easy to crash. With FFM, the *entire* binding is pure Java: a descriptor, a lookup, a method handle. That, plus `Arena`/`MemorySegment` safety, is why FFM supersedes JNI for most use cases.
- What is the difference between a downcall and an upcall?A downcall is Java invoking a native C function (via a downcall MethodHandle). An upcall is the reverse: a Java method exposed as a native function pointer (an upcall stub) so native code can call back into Java.
- Why does upcallStub take an Arena?The arena governs the stub's lifetime; when it closes, the native function pointer is freed/invalidated, so native code must not call it afterward.
- What does FunctionDescriptor capture and why is it required?It captures the C function's signature (return and argument layouts) because C functions aren't self-describing — the linker needs the ABI types to marshal arguments and the return value correctly.
saying these in an interview costs you the question
- Forgetting the FunctionDescriptor / getting argument or return types wrong (corrupts the call)
- Letting C retain an upcall stub after its arena closes (dangling function pointer)
- Thinking you still need handwritten C glue like JNI
- Mapping a C pointer to something other than ValueLayout.ADDRESS
- Ignoring errno capture and then reading a clobbered errno