Java 10 Removed javah: Generate JNI Headers with javac -h
Introduction
Java code can call functions written in C or C++ through the Java Native Interface (JNI). A Java class declares such functions as
A class with native methods
The class below declares three native methods - an instance method, a static method, and two overloads of
Generating the header with javac -h
This creates one header per class with native methods, named after the fully qualified class name with the dots replaced by underscores. Here it is
Understanding the generated header
Implementing the native methods
The C file includes the generated header and implements its functions. Below is a sketch of
The C file is compiled into a shared library with the JDK's
Note - For new code, also consider the Foreign Function & Memory API, a standard feature since Java 22. It calls native libraries from pure Java code, without writing any C glue code or generating headers.
Updating builds which used javah
Builds which ran
In Gradle, the
Summary
In this tutorial we saw that Java 10 removed the
native methods, and their C implementations must use exact function names and signatures, which are listed in a C header file generated from the class. For years this header was generated by a separate tool, javah. Java 10Click to read tutorial: Overview of New Features in Java 10 removed javah (JEP 313), and the header is now generated by javac itself, with its -h option. This tutorial explains how to generate JNI headers with javac -h. We will first see why javah was removed. We will then generate a header for a class with native methods, and understand the generated function names and signatures. Lastly, we will update Maven and Gradle builds which used javah.
Why javah was removed
javah read compiled .class files, so generating a header was always a separate step after compilation, which build tools had to run with the right class path. Java 8 added the -h <directory> option to javac, which writes the headers while compiling the source code, in the same step. With that, javah was no longer needed. It was deprecated in Java 9 and removed in Java 10. Running javah on Java 10 or later fails, since the command no longer exists.
add(). Native methods have no body, just like abstract methods. The static block loads the native library containing their implementation when the class is first used.
A class with native methods
//NativeGreeter.java
package com.javabrahman.jni;
public class NativeGreeter {
public static final int MAX_NAME_LENGTH = 64;
static {
System.loadLibrary("greeter"); // loads greeter.dll, libgreeter.so or libgreeter.dylib
}
public native String greet(String name);
public static native long nanoTime();
public native int add(int a, int b);
public native long add(long a, long b);
}
-h takes the directory where the headers are written. The class files go, as usual, to the directory given with -d -
javac -h native -d classes NativeGreeter.java
native/com_javabrahman_jni_NativeGreeter.h -
GENERATED com_javabrahman_jni_NativeGreeter.h/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_javabrahman_jni_NativeGreeter */
#ifndef _Included_com_javabrahman_jni_NativeGreeter
#define _Included_com_javabrahman_jni_NativeGreeter
#ifdef __cplusplus
extern "C" {
#endif
#undef com_javabrahman_jni_NativeGreeter_MAX_NAME_LENGTH
#define com_javabrahman_jni_NativeGreeter_MAX_NAME_LENGTH 64L
/*
* Class: com_javabrahman_jni_NativeGreeter
* Method: greet
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_javabrahman_jni_NativeGreeter_greet
(JNIEnv *, jobject, jstring);
/*
* Class: com_javabrahman_jni_NativeGreeter
* Method: nanoTime
* Signature: ()J
*/
JNIEXPORT jlong JNICALL Java_com_javabrahman_jni_NativeGreeter_nanoTime
(JNIEnv *, jclass);
/*
* Class: com_javabrahman_jni_NativeGreeter
* Method: add
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_com_javabrahman_jni_NativeGreeter_add__II
(JNIEnv *, jobject, jint, jint);
/*
* Class: com_javabrahman_jni_NativeGreeter
* Method: add
* Signature: (JJ)J
*/
JNIEXPORT jlong JNICALL Java_com_javabrahman_jni_NativeGreeter_add__JJ
(JNIEnv *, jobject, jlong, jlong);
#ifdef __cplusplus
}
#endif
#endif
- Function names are made of
Java_, the fully qualified class name with underscores, and the method name - sogreet()becomesJava_com_javabrahman_jni_NativeGreeter_greet. The JVM uses these names to find the C function for each native method. - Overloaded methods need different C names, so the argument types are encoded after a double underscore.
add(int, int)becomes..._add__IIandadd(long, long)becomes..._add__JJ, whereIandJare the JVM's type descriptors forintandlong. Methods which are not overloaded keep the short name. - The first two parameters are added by JNI.
JNIEnv *gives the C code access to JNI functions, such as converting strings. The second is ajobject- thethisobject - for an instance method, and ajclass- the class - for a static method likenanoTime(). - Java types map to JNI types -
Stringtojstring,inttojint,longtojlong, and so on. - The
Signaturecomment shows each method's JVM descriptor -(Ljava/lang/String;)Ljava/lang/String;means "takes aString, returns aString". - The
static finalconstantMAX_NAME_LENGTHwas written as a C#define, prefixed with the class name, so the C code can use the same value as the Java code. - The
extern "C"block lets the same header be used from C++ as well as C.
greet() and one add() -
greeter.c (implementation of two native methods)
#include <stdio.h>
#include "com_javabrahman_jni_NativeGreeter.h"
JNIEXPORT jstring JNICALL Java_com_javabrahman_jni_NativeGreeter_greet
(JNIEnv *env, jobject self, jstring name) {
const char *cname = (*env)->GetStringUTFChars(env, name, NULL);
char buffer[com_javabrahman_jni_NativeGreeter_MAX_NAME_LENGTH + 16];
snprintf(buffer, sizeof buffer, "Hello, %s!", cname);
(*env)->ReleaseStringUTFChars(env, name, cname);
return (*env)->NewStringUTF(env, buffer);
}
JNIEXPORT jint JNICALL Java_com_javabrahman_jni_NativeGreeter_add__II
(JNIEnv *env, jobject self, jint a, jint b) {
return a + b;
}
include directories on the compiler's include path, and the library is placed where System.loadLibrary() can find it - in a directory on java.library.path. For example, on Linux with GCC -
gcc -shared -fPIC -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" -Inative greeter.c -o libgreeter.so java -Djava.library.path=. -cp classes com.javabrahman.jni.SomeMainClass
javah as a separate step should instead pass -h to the compiler. In Maven, add it to the compiler plugin's arguments, and remove any plugin which ran javah -
Maven - generating JNI headers during compilation
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<compilerArgs>
<arg>-h</arg>
<arg>${project.build.directory}/native/include</arg>
</compilerArgs>
</configuration>
</plugin>
JavaCompile task has a property for this -
Gradle - generating JNI headers during compilation
tasks.named('compileJava') {
options.headerOutputDirectory = layout.buildDirectory.dir('native/include')
}
javah tool, since javac -h has generated JNI headers while compiling since Java 8. We generated a header for a class with native methods, and went through how JNI builds the C function names, including for overloaded methods, and the extra JNIEnv and jobject or jclass parameters. We then implemented two of the functions in C, and lastly updated Maven and Gradle builds to use -h. In the next tutorial, we will look at the Unicode locale extensionsClick to read tutorial: Java 10 Unicode Locale Extensions: Currency, Week, Region added in Java 10.Continue Reading Java 9 & Java 10 Tutorials
Java 10 Unicode Locale Extensions: Currency, Week, RegionClick to read tutorial: Java 10 Unicode Locale Extensions: Currency, Week, Region Java 10 Time-Based Release Versioning and Runtime.VersionClick to read tutorial: Java 10 Time-Based Release Versioning and Runtime.Version Overview of New Features in Java 10Click to read tutorial: Overview of New Features in Java 10 Java var Keyword: Local Variable Type InferenceClick to read tutorial: Java var Keyword: Local Variable Type Inference Java 10 API Additions: copyOf, toUnmodifiableList & MoreClick to read tutorial: Java 10 API Additions: copyOf, toUnmodifiableList & More Java 10 Application Class-Data Sharing (AppCDS) TutorialClick to read tutorial: Java 10 Application Class-Data Sharing (AppCDS) Tutorial Java 9 Modules Tutorial | Java Platform Module System (JPMS)Click to read tutorial: Java 9 Modules Tutorial | Java Platform Module System (JPMS)
Java 10 Unicode Locale Extensions: Currency, Week, RegionClick to read tutorial: Java 10 Unicode Locale Extensions: Currency, Week, Region Java 10 Time-Based Release Versioning and Runtime.VersionClick to read tutorial: Java 10 Time-Based Release Versioning and Runtime.Version Overview of New Features in Java 10Click to read tutorial: Overview of New Features in Java 10 Java var Keyword: Local Variable Type InferenceClick to read tutorial: Java var Keyword: Local Variable Type Inference Java 10 API Additions: copyOf, toUnmodifiableList & MoreClick to read tutorial: Java 10 API Additions: copyOf, toUnmodifiableList & More Java 10 Application Class-Data Sharing (AppCDS) TutorialClick to read tutorial: Java 10 Application Class-Data Sharing (AppCDS) Tutorial Java 9 Modules Tutorial | Java Platform Module System (JPMS)Click to read tutorial: Java 9 Modules Tutorial | Java Platform Module System (JPMS)