00:00

native Keyword in Java – Native Methods and JNI

The native keyword marks a method that is declared in Java but implemented in another language, usually C or C++. The method has no body in Java; at run time the JVM links it to a function in a native library through the Java Native Interface (JNI).

This tutorial explains the rules for native methods, walks through a complete working JNI example, shows native methods inside the JDK itself, and introduces the modern Foreign Function & Memory (FFM) API that replaces most hand-written JNI.

1. What Is a native Method?

A native method looks like an abstract method: a signature followed by a semicolon. The difference is that its implementation exists, just outside Java:

Syntax
[access modifier] [static] native returnType methodName(parameters);

public native int add(int a, int b);
private static native long checksum(byte[] data);

Typical reasons to use native code:

  • Accessing operating-system or hardware features that Java does not expose.
  • Reusing existing C or C++ libraries (graphics, codecs, scientific code, drivers).
  • Very performance-critical routines, although the modern JIT often makes this unnecessary.

2. native Methods Inside the JDK

You use native methods every day. Core parts of the JDK are implemented in native code inside the JVM:

NativeMethodsInJdk.java
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

public class NativeMethodsInJdk {
    public static void main(String[] args) throws Exception {
        check(Object.class.getMethod("hashCode"));
        check(Object.class.getMethod("getClass"));
        check(System.class.getMethod("currentTimeMillis"));
        check(System.class.getMethod("nanoTime"));
        check(System.class.getMethod("arraycopy", Object.class, int.class, Object.class, int.class, int.class));
        check(String.class.getMethod("length"));
    }

    static void check(Method m) {
        System.out.printf("%-35s native? %s%n",
                m.getDeclaringClass().getSimpleName() + "." + m.getName() + "()",
                Modifier.isNative(m.getModifiers()));
    }
}
Output
Object.hashCode()                   native? true
Object.getClass()                   native? true
System.currentTimeMillis()          native? true
System.nanoTime()                   native? true
System.arraycopy()                  native? true
String.length()                     native? false

3. A Complete JNI Example

Writing your own native method takes four steps: declare it in Java, generate a C header, implement it in C, and load the compiled library.

Step 1: Declare the native methods and load the library

HelloJNI.java
public class HelloJNI {
    static {
        System.loadLibrary("hello");   // loads libhello.so / libhello.dylib / hello.dll
    }

    // Declared in Java, implemented in C
    private native void sayHello(String name);
    private native int add(int a, int b);

    public static void main(String[] args) {
        HelloJNI app = new HelloJNI();
        app.sayHello("Asha");
        System.out.println("3 + 4 = " + app.add(3, 4));
    }
}

System.loadLibrary("hello") looks for libhello.dylib on macOS, libhello.so on Linux or hello.dll on Windows in the directories listed in java.library.path. It is usually placed in a static block so the library loads once, when the class is initialised.

Step 2: Generate the C header

javac -h . HelloJNI.java produces a header with the exact C function names and signatures the JVM expects:

HelloJNI.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class HelloJNI */

#ifndef _Included_HelloJNI
#define _Included_HelloJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     HelloJNI
 * Method:    sayHello
 * Signature: (Ljava/lang/String;)V
 */
JNIEXPORT void JNICALL Java_HelloJNI_sayHello
  (JNIEnv *, jobject, jstring);

/*
 * Class:     HelloJNI
 * Method:    add
 * Signature: (II)I
 */
JNIEXPORT jint JNICALL Java_HelloJNI_add
  (JNIEnv *, jobject, jint, jint);

#ifdef __cplusplus
}
#endif
#endif

Step 3: Implement the functions in C

HelloJNI.c
#include <jni.h>
#include <stdio.h>
#include "HelloJNI.h"

JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *env, jobject obj, jstring name) {
    const char *cname = (*env)->GetStringUTFChars(env, name, NULL);
    printf("Hello from C, %s!\n", cname);
    fflush(stdout);
    (*env)->ReleaseStringUTFChars(env, name, cname);
}

JNIEXPORT jint JNICALL Java_HelloJNI_add(JNIEnv *env, jobject obj, jint a, jint b) {
    return a + b;
}

Every JNI function receives a JNIEnv* (the gateway to JVM services such as converting strings) and the object (jobject) or class (jclass, for static methods) it was called on. Java types map to C types such as jint, jstring and jobject.

Step 4: Build the library and run

build-jni.sh
# 1. Compile the Java class and generate the C header (HelloJNI.h)
javac -h . HelloJNI.java

# 2. Compile the C code into a shared library
# macOS
clang -shared -fPIC -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/darwin" HelloJNI.c -o libhello.dylib
# Linux
gcc -shared -fPIC -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" HelloJNI.c -o libhello.so
# Windows (MSVC): cl /LD /I"%JAVA_HOME%\include" /I"%JAVA_HOME%\include\win32" HelloJNI.c /Fehello.dll

# 3. Run, telling the JVM where the library is
java -Djava.library.path=. HelloJNI
Output
Hello from C, Asha!
3 + 4 = 7

If the library cannot be found, the call fails with java.lang.UnsatisfiedLinkError: no hello in java.library.path.

4. Rules for native Methods

  • native can be applied only to methods, not to classes, fields or constructors.
  • A native method has no body; it ends with a semicolon.
  • It cannot be abstract (it has an implementation) and could not be strictfp before Java 17.
  • It can be static, final, synchronized and have any access modifier.
  • It can be overridden by a Java method in a subclass, and can override a Java method.
  • It can throw exceptions; native code raises them through JNIEnv functions such as ThrowNew.

5. Advantages and Disadvantages

AdvantagesDisadvantages
Access to OS and hardware featuresLoses platform independence: a library must be built for every OS and CPU
Reuse of existing native librariesA bug in native code can crash the entire JVM
Possible speed-ups for special workloadsNo garbage collection or bounds checking in native memory; leaks are easy
Calls across the boundary have overhead; JNI code is verbose and hard to debug

6. The Modern Alternative: Foreign Function & Memory API

Java 22 finalised the FFM API (java.lang.foreign, JEP 454). It calls native functions directly from Java, with no C glue code, no header generation and no native keyword:

FfmStrlen.java (Java 22+)
import java.lang.foreign.*;
import java.lang.invoke.MethodHandle;

public class FfmStrlen {
    public static void main(String[] args) throws Throwable {
        Linker linker = Linker.nativeLinker();
        MethodHandle strlen = linker.downcallHandle(
                linker.defaultLookup().find("strlen").orElseThrow(),
                FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS));

        try (Arena arena = Arena.ofConfined()) {
            MemorySegment text = arena.allocateFrom("JavaByTechie");
            long len = (long) strlen.invokeExact(text);
            System.out.println("strlen = " + len);   // strlen = 12
        }
    }
}

For new code that needs native libraries, prefer FFM (and the jextract tool that generates bindings from C headers). JNI remains supported, and since Java 24 the JVM warns when native access is used without --enable-native-access.

7. Summary

  • native declares a method whose implementation is in C/C++ or another language.
  • JNI connects Java and native code: declare, generate a header with javac -h, implement, then System.loadLibrary.
  • Native methods have no body and cannot be abstract; they can be static, final or synchronized.
  • Native code sacrifices portability and safety, so use it only when needed.
  • On Java 22+, the FFM API is the simpler, safer way to call native libraries.

Related Tutorials