Skip to content
AITroveRead. Build. Understand.
Make this comfortable

Java native Linker: declare a C call and reject undefined input

Last updated: 29 Sept 20264 min read
tutorial
IntermediateBy AITrove Editorial

A native Linker creates a method handle for a foreign function using a descriptor that matches the platform calling convention.

Download Java source kit

This complete program targets Java 25. Its displayed output is checked by the tutorial validation script.

Match the foreign signature

The fixture calls the platform C abs function for a bounded signed adjustment. It obtains the canonical C int layout from the linker instead of assuming that every foreign integer type has the Java long layout. A signature mismatch at this boundary can corrupt execution rather than produce an ordinary type error.

The program rejects Integer.MIN_VALUE before entering C, because its positive magnitude does not fit the same signed int representation. That condition is an input rule, not an exception the native function is expected to throw back into Java. The checked fixture uses a Java int carrier on this platform.

Grant access deliberately

Run the downloaded class with --enable-native-access=ALL-UNNAMED because this kit uses the classpath. A named-module application should grant the intended module rather than copying that flag indiscriminately. Native access is a trust decision; it does not validate a function descriptor or make foreign code memory-safe.

The checked call has no pointers or allocated foreign buffers. Larger APIs need an ownership rule for each pointer and callback, including how long the callee retains it. Memory segments and Shared arena lifetime cover separate parts of that contract.

Working program

Java
import java.lang.foreign.*;
import java.lang.invoke.MethodHandle;
public class NativeAdjustmentMagnitude {
    static int magnitude(MethodHandle operation,int adjustment) throws Throwable {
        if (adjustment == Integer.MIN_VALUE) throw new IllegalArgumentException("magnitude exceeds signed int");
        return (int) operation.invokeExact(adjustment);
    }
    public static void main(String[] args) throws Throwable {
        Linker linker=Linker.nativeLinker();
        MemoryLayout integer=linker.canonicalLayouts().get("int");
        if (!(integer instanceof ValueLayout.OfInt)) throw new UnsupportedOperationException("C int carrier");
        MethodHandle operation=linker.downcallHandle(linker.defaultLookup().find("abs").orElseThrow(),
            FunctionDescriptor.of(integer,integer));
        System.out.println(magnitude(operation,-125));
        System.out.println(magnitude(operation,0));
        try {magnitude(operation,Integer.MIN_VALUE);}
        catch(IllegalArgumentException rejected){System.out.println("range rejected");}
    }
}

Output

Output
125
0
range rejected

Costs and boundaries

The scalar operation has bounded work, but downcall setup and foreign-transition costs are not measured here. The example is checked on the local platform, not every ABI. It does not make a claim that native abs is a better choice than Java arithmetic.

Common Mistakes

  • Match the actual C signature and carrier.
  • Reject values outside the foreign function contract before calling it.
  • Native access does not sandbox a foreign library.

Read next

Java foreign memory: arena lifetime, bounds and thread ownership, Java shared Arena: coordinate access before closing memory, Java VarHandle: atomic transitions and explicit access modes.

java
native-linker
Storage details