Skip to content

Add "Calling out to C code from Java" pattern: JNI vs FFM API#130

Merged
brunoborges merged 7 commits intomainfrom
copilot/simplify-c-code-java
Mar 3, 2026
Merged

Add "Calling out to C code from Java" pattern: JNI vs FFM API#130
brunoborges merged 7 commits intomainfrom
copilot/simplify-c-code-java

Conversation

Copy link
Contributor

Copilot AI commented Mar 2, 2026

Adds a new advanced language pattern contrasting the legacy JNI approach with the modern Foreign Function & Memory (FFM) API introduced in Java 22, highlighting how FFM eliminates the need for C-side Java awareness entirely.

New pattern: content/language/call-c-from-java.yaml

Both the old and modern sides call strlen("Bambi") and print "Return value 5", making them directly comparable:

  • Old (JNI): declare native long strlen(String s) → run javac -h → write a C wrapper using JNI conventions (JNIEnv*, GetStringUTFChars, ReleaseStringUTFChars, etc.) → compile and link a .so
  • Modern (FFM): call strlen directly from the C stdlib via Linker, Arena, and MethodHandle — no C code required at all

The comparison highlights that JNI can't even call a simple stdlib function without a C wrapper, while FFM calls it directly:

void main() throws Throwable {
    try (var arena = Arena.ofConfined()) {
        var stdlib = Linker.nativeLinker().defaultLookup();
        var foreignFuncAddr = stdlib.find("strlen").orElseThrow();
        var strlenSig =
            FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS);
        var strlenMethod =
            Linker.nativeLinker().downcallHandle(foreignFuncAddr, strlenSig);
        var ret = (long) strlenMethod.invokeExact(arena.allocateFrom("Bambi"));
        IO.println("Return value " + ret); // 5
    }
}

Navigation chain

  • compact-canonical-constructorcall-c-from-javaenterprise/servlet-vs-jaxrs

Proof script: proof/language/CallCFromJava.java

  • Calls strlen via FFM; uses //JAVA_OPTIONS --enable-native-access=ALL-UNNAMED

Translations

  • Added for all 12 non-English locales: de, es, pt-BR, zh-CN, ar, fr, ja, ko, bn, it, pl, tr
  • Technical terms (JNI, FFM, javac, JNIEnv, MethodHandle, Arena, JDK 22) are preserved untranslated in all locales
Original prompt

This section details on the original issue you should resolve

<issue_title>[Pattern]</issue_title>
<issue_description>### Category

language

Slug

call-c-from-java

Title

Calling out to C code from Java

Difficulty

advanced

Since JDK

22

Summary

Simplifies C/C++ code to be called from Java, allowing most existing libraries to be used without adapters or cumbersome C code modification.

Old code label

Java 1.1+

Old code

public class CallCFromJava {
	static { System.loadLibrary("hello-jni"); }
	public static native int greet(String name);
	public static void main(String[] args) {
		int ret = greet("Bambi");
		System.out.println("Return value " + ret);
	}
}

Then run javac -h, then view the generated .h file, then write your .c file:

#include "CallCFromJava.h"

JNIEXPORT int JNICALL Java_CallCFromJava_greet(JNIEnv *env, jclass this, jstring str) {
	const char* name = (*env)->GetStringUTFChars(env, str, NULL);
	printf("Hello %s\n", name);
	return 0;
}

Modern code label

Java 22+

Modern code

public class CallCFromJava {
  void main() throws Throwable {

    try (Arena arena = Arena.ofConfined()) {
      SymbolLookup ourLib = 
        SymbolLookup.libraryLookup("libhello-ffi.so", arena);
      Optional<MemorySegment> segment = ourLib.find("greet");
      MemorySegment foreignFuncAddr = segment.get();
      FunctionDescriptor greet_sig= // return and input parameter
          FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS);
      MethodHandle greetMethod =
        Linker.nativeLinker().downcallHandle(foreignFuncAddr, greet_sig);
      var ret = (long) greetMethod.invokeExact(arena.allocateFrom("Bambi"));
      System.out.println("Return value " + ret);
    }
  }
}

C Code:
int greet(char* name) {
    printf("Hello %s\n", name);
    return 0;
}

Explanation

Java has two techniques for interfacing between Java and C/C++ code, the traditional JNI and the newer FFM. With JNI you simply declare your method as native, but then you have to run javac -h to generate a C-language .h (header) file, then have your C code implement that, and deal with the slightly cumbersome JNI C-language API.

FFM simplifies the C code to the point that it doesn't have to know anything about Java. This makes it much easier to interface with existing C/C++-language libraries. It does take a few more lines of Java code, but the net effect is more readable and more maintainable. Compare the C code in both versions and you'll see the difference!

Minimal runnable demo can be found at https://github.com/IanDarwin/javaevolved-java_callling_c

Why the modern way wins

👁 Better readability — C code doesn't have to learn Java!
⚡ More flexible - can directly call most existing C/C++ libraries
⚡ Easier - don't have to stop, run javac -h, and implement the "interface" defined in .h file.</issue_description>

Comments on the Issue (you are @copilot in this section)

@brunoborges @IanDarwin what do you think of this code snippet for the Modern side?
void main() throws Throwable {

    try (var arena = Arena.ofConfined()) {
        // Use a system library to prove FFM compiles and links
        var stdlib = Linker.nativeLinker().defaultLookup();
        var foreignFuncAddr = stdlib.find("strlen").orElseThrow();
        var strlen_sig =
            FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS);
        var strlenMethod =
            Linker.nativeLinker().downcallHandle(foreignFuncAddr, strlen_sig);
        var ret = (long) strlenMethod.invokeExact(arena.allocateFrom("Bambi"));
        IO.println("Return value " + ret);
    }

}

💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Co-authored-by: brunoborges <129743+brunoborges@users.noreply.github.com>
Copilot AI changed the title [WIP] Simplify C/C++ code integration with Java Add "Calling out to C code from Java" pattern: JNI vs FFM API Mar 2, 2026
@brunoborges
Copy link
Member

@copilot now add:

  • Translations
  • Proof

Copilot AI and others added 2 commits March 3, 2026 00:38
Adds YAML translation files for de, es, pt-BR, zh-CN, ar, fr, ja, ko,
bn, it, pl, tr. Each file contains only translatable fields. Technical
terms (JNI, FFM, javac, JNIEnv, MethodHandle, Arena, JDK 22) are
unchanged in all languages.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: brunoborges <129743+brunoborges@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Mar 3, 2026

@copilot now add:

  • Translations
    ...

Both are now complete:

  • Proof (proof/language/CallCFromJava.java): calls strlen from stdlib via FFM API — self-contained, no custom .so needed (commit 10d4c4a)
  • Translations: added for all 12 non-English locales — de, es, pt-BR, zh-CN, ar, fr, ja, ko, bn, it, pl, tr (commits 48a7ac6, 37c1bc9)

@brunoborges brunoborges marked this pull request as ready for review March 3, 2026 06:07
@brunoborges
Copy link
Member

@copilot is the old code comparable in functionality with the modern code?

Co-authored-by: brunoborges <129743+brunoborges@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Mar 3, 2026

@copilot is the old code comparable in functionality with the modern code?

They weren't — the old code called a custom greet() function (returning 0), while the modern code called strlen (returning 5). Fixed in 361f9db: both sides now call strlen("Bambi") and print "Return value 5".

This actually makes the comparison stronger: JNI can't call strlen directly — you still have to write a C wrapper using JNIEnv, GetStringUTFChars, etc. FFM calls it directly from Java with no C code at all.

@brunoborges brunoborges merged commit d3b4379 into main Mar 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Pattern]

2 participants