Skip to content

Commit 10d4c4a

Browse files
Copilotbrunoborges
andcommitted
Add call-c-from-java pattern (JNI vs FFM API)
Co-authored-by: brunoborges <129743+brunoborges@users.noreply.github.com>
1 parent 0bb01fc commit 10d4c4a

File tree

3 files changed

+106
-1
lines changed

3 files changed

+106
-1
lines changed
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
---
2+
id: 113
3+
slug: "call-c-from-java"
4+
title: "Calling out to C code from Java"
5+
category: "language"
6+
difficulty: "advanced"
7+
jdkVersion: "22"
8+
oldLabel: "Java 1.1+"
9+
modernLabel: "Java 22+"
10+
oldApproach: "JNI (Java Native Interface)"
11+
modernApproach: "FFM (Foreign Function & Memory API)"
12+
oldCode: |-
13+
public class CallCFromJava {
14+
static { System.loadLibrary("hello-jni"); }
15+
public static native int greet(String name);
16+
public static void main(String[] args) {
17+
int ret = greet("Bambi");
18+
System.out.println("Return value " + ret);
19+
}
20+
}
21+
22+
// Run javac -h to generate the .h file, then write C:
23+
// JNIEXPORT int JNICALL Java_CallCFromJava_greet(
24+
// JNIEnv *env, jclass clazz, jstring str) {
25+
// const char* name =
26+
// (*env)->GetStringUTFChars(env, str, NULL);
27+
// printf("Hello %s\n", name);
28+
// return 0;
29+
// }
30+
modernCode: |-
31+
void main() throws Throwable {
32+
try (var arena = Arena.ofConfined()) {
33+
// Use any system library directly — no C wrapper needed
34+
var stdlib = Linker.nativeLinker().defaultLookup();
35+
var foreignFuncAddr =
36+
stdlib.find("strlen").orElseThrow();
37+
var strlenSig =
38+
FunctionDescriptor.of(
39+
ValueLayout.JAVA_LONG, ValueLayout.ADDRESS);
40+
var strlenMethod =
41+
Linker.nativeLinker()
42+
.downcallHandle(foreignFuncAddr, strlenSig);
43+
var ret = (long) strlenMethod.invokeExact(
44+
arena.allocateFrom("Bambi"));
45+
System.out.println("Return value " + ret); // 5
46+
}
47+
}
48+
49+
// Your own C library needs no special Java annotations:
50+
// long greet(char* name) {
51+
// printf("Hello %s\n", name);
52+
// return 0;
53+
// }
54+
summary: "FFM lets Java call C libraries directly, without JNI boilerplate or C-side Java knowledge."
55+
explanation: "Java has two approaches for calling native C/C++ code: the traditional\
56+
\ JNI and the modern FFM API. With JNI, you declare a method as native, run javac -h\
57+
\ to generate a C header file, then implement the function using the cumbersome JNI\
58+
\ C API (JNIEnv, jstring, etc.). FFM, introduced as a standard API in Java 22,\
59+
\ eliminates all of that: C code is just plain C — no JNI conventions needed. This\
60+
\ makes it far easier to call existing C/C++ libraries without modification. The\
61+
\ Java side uses Arena for safe off-heap memory management and MethodHandle for the\
62+
\ downcall, ensuring both flexibility and safety."
63+
whyModernWins:
64+
- icon: "👁"
65+
title: "C code stays plain C"
66+
desc: "The C function requires no JNI annotations or JNIEnv boilerplate — any existing C library can be called as-is."
67+
- icon: ""
68+
title: "More flexible"
69+
desc: "Directly call most existing C/C++ libraries without writing adapter code or generating header files."
70+
- icon: "🛠️"
71+
title: "Easier workflow"
72+
desc: "No need to stop, run javac -h, and implement the interface defined in the generated .h file."
73+
support:
74+
state: "available"
75+
description: "Standardized in JDK 22 (March 2024); previously incubating since JDK 14"
76+
prev: "language/compact-canonical-constructor"
77+
next: "enterprise/servlet-vs-jaxrs"
78+
related:
79+
- "io/file-memory-mapping"
80+
- "language/compact-source-files"
81+
- "language/unnamed-variables"
82+
docs:
83+
- title: "JEP 454: Foreign Function & Memory API"
84+
href: "https://openjdk.org/jeps/454"
85+
- title: "java.lang.foreign package (Java 22)"
86+
href: "https://docs.oracle.com/en/java/javase/22/docs/api/java.base/java/lang/foreign/package-summary.html"

content/language/compact-canonical-constructor.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ support:
4848
state: "available"
4949
description: "Widely available since JDK 16 (March 2021)"
5050
prev: "language/static-members-in-inner-classes"
51-
next: "enterprise/servlet-vs-jaxrs"
51+
next: "language/call-c-from-java"
5252
related:
5353
- "language/records-for-data-classes"
5454
- "language/flexible-constructor-bodies"

proof/language/CallCFromJava.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
///usr/bin/env jbang "$0" "$@" ; exit $?
2+
//JAVA 25+
3+
//JAVA_OPTIONS --enable-native-access=ALL-UNNAMED
4+
import java.lang.foreign.*;
5+
6+
/// Proof: call-c-from-java
7+
/// Source: content/language/call-c-from-java.yaml
8+
void main() throws Throwable {
9+
try (var arena = Arena.ofConfined()) {
10+
var stdlib = Linker.nativeLinker().defaultLookup();
11+
var foreignFuncAddr = stdlib.find("strlen").orElseThrow();
12+
var strlenSig =
13+
FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS);
14+
var strlenMethod =
15+
Linker.nativeLinker().downcallHandle(foreignFuncAddr, strlenSig);
16+
var ret = (long) strlenMethod.invokeExact(arena.allocateFrom("Bambi"));
17+
assert ret == 5 : "Expected strlen(\"Bambi\") == 5, got " + ret;
18+
}
19+
}

0 commit comments

Comments
 (0)