The Java Native Interface
by Prithvi Rao
Prithvi Rao is the founder of Kiwilabs, which specializes in software
engineering methodology and Java training. He has worked on the
development of the MACH OS and a real-time version of MACH, and he
holds two patents resulting from his work on mobile robots.
This capability is mandated in several situations. For instance, in order to run Abstract Windowing ToolKit (AWT) applications, the graphics context for the application has to eventually make "native" calls to the libraries that perform the graphics operations. It is likely that these libraries are written in C (more likely than C++). Also, Java communication packages eventually make "native" calls; we can gather from this that "native" refers to either C or C++. Finally, it is likely that most organizations have significant amounts of C and C++ code that has been debugged and tested. JNI plays a vital role in supporting the use of this code.
Let's start with a summary of the main considerations of JNI, then present examples of code and a more detailed description of this capability.
Java Native Interface (JNI)
The Java Native Interface is a standard programming interface between Java and native programs. Typically, native programs are used when they cannot be written in Java (legacy code, performance considerations).
The JNI makes it possible to create, inspect, and update Java objects including arrays and strings. The JNI also permits the catching and throwing of exceptions, loads classes, obtains class information, and performs runtime type checking. It is possible using JNI to link a Java Virtual Machine (JVM) into non-Java applications.
As with any important technology, interested parties will jostle for acceptance of their ideas. Sun, with its JNI, is one of three currently trying to influence the Native Interface standard. The other two main parties who can influence the standard are Netscape with the Java Runtime Interface (JRI) and Microsoft with the Raw Native Interface (RNI). The standard is still evolving, and this article will discuss only JNI from Sun.
The advantages of a standard are that each vendor can support a large body of native code. Also, tool builders need not maintain different kinds of native method interfaces. This means that application programmers write one version of their native code. Some requirements of the evolving standard are:
It appears that JNI is becoming the standard, so it is probably a good idea to program to it.
Working with Native Methods
The steps involved in using JNI are:
Let's take a look at an example of how JNI can be used to call C programs from Java. (In a future article I will discuss how to do the reverse.) We will look at a package to manipulate rational numbers. The package supplies the following routines:
We will create a Java class called Rational:
public class Rational
public Rational(int n, int d)
public static Rational RationalAdd(Rational r1, Rational
public static Rational RationalMul(Rational r1, Rational r2)
public static Rational RationalDiv(Rational r1, Rational
Next we define the native methods:
public static native int add(int r1, int r2);
This says that there is a function called "add" which is not written in Java.
public static native int sub(int r1, int r2);
public static native int mul(int r1, int r2);
public static native int div(int r1, int r2);
public static native int gcd(int x, int y);
Next we create a toString method. This is always a good idea:
public String toString()
return new String(sb);
Now we need to add code to load the shared library:
This call loads the librat.so (Solaris) shared library so that Java programs can invoke the native calls.
Now that we have the Rational class defined we can compile the code:
This produces a .class file from which we create a header file which acts as a "bridge" between the Java and C code.
javah -jni Rational
The output of this is a file "Rational.h". The entries in this file must not be edited, because the JVM uses it to interpret the parameters being passed between Java and the native code.
For our native function "add" the entry in this file looks like this:
Every native function will have a similar entry based on its signature. We will discuss the details in a subsequent article.
Next we write the C code that implements the native functions. We include only the "add" example here. Readers can send mail to <firstname.lastname@example.org> for a full code example.
int *radd(int *, int *);
JNIEXPORT jintArray JNICALL
(*jenv)->ReleaseIntArrayElements(jenv, jr1, r1arr,
r = (jint *) radd((int *) r1arr, (int *) r2arr);
The above code example is the native code implementation of "add". In subsequent articles I will cover in more detail the writing of native functions. For now it is important to note that native code includes the "jni.h" and "Rational.h" include files. These two files are crucial because they form the "bridge" which permits the Java native programs to pass parameters between them.
JNI and Threading
Since Java is a multithreaded language, several threads can call a native method concurrently. However, it is possible that a native method might be suspended in the middle of its operation when a second thread calls it. It is entirely up to the programmer to guarantee that the native call is thread-safe. One way of doing this is to declare the native thread as "synchronized" or implement some other strategy within the native method to ensure correct concurrent data manipulation.
Another consideration of multithreading is that the JNIEnv pointer must not be passed across threads. This is because the internal structure to which it points is allocated on a per-thread basis and so contains information that makes sense only in that particular thread.
JNI and Java Exceptions
With JNI, Java exceptions can be thrown, caught, printed, and rethrown just as they are inside a Java program. But it's up to the programmer to call dedicated JNI functions to deal with exceptions. Some JNI functions for exception handling are:
Of these exceptions, it is not possible to ignore ExceptionOccurred() and ExceptionClear(). After exceptions are handled by ExceptionOccurred() it is necessary to clear them using ExceptionClear() to avoid unpredictable results (especially if a call is made to a JNI function while an exception is pending).
We have looked at the use of JNI for calling native methods written in C from a Java program. Clearly, this is a very important consideration in Java being a credible language; Java could not be taken seriously were this not possible. It is equally important to be able to invoke Java methods from native code, and this is also possible.
A strength of Java is that not only is native code integration possible, but it is simple and straightforward with all the tools available as part of the JDK distribution. However, it is still necessary to understand the use of JNI within the larger context of multithreading and exception handling, which have important implications when you are working with native interfaces.