A Simple Java Native Interface (JNI) example in Java and Scala

Brian Schlining
4 min readOct 28, 2017

--

The Java Native Interface (JNI) is a framework that allows your Java code to call native applications and libraries written in languages such as C, C++ and Objective-C. To be honest, if you have any other choice besides using JNI, do that other thing. Dealing with native libraries is a huge source of pain when supporting Java applications. With that said, sometimes you just can’t avoid using JNI. This article outlays the relative simple steps needed to write a JNI bridge.

Step1 — Create a Java or Scala class

Write a Java or Scala class that will interface with the native library.

Java

In Java, methods that will call the native code will use the native keyword in their method signature and have no method body. Create a file named Sample1.java and add the code below:

Scala

In Scala, methods that call native code will use the native annotation and, like Java, have no method body. Create a file named Sample1.scala and add the code below:

Step 2 — Compile the Class /Generate a Header File

Now that you have some code with native methods, you can use the JDK to compile it and produce a header file that defines the function prototypes needed for the native implementation.

Java 7 and before

In the olden days, i.e. pre-Java 9, you would first compile the class file using javac, then generate the header file using javah. This looks like:

# Compile
javac Sample1.java
# Generate the C/C++ header using Sample1.class. Note, you omit the
# '.class' extension below and simply use 'Sample1'
javah Sample1

Java 8+

As of Java 9, javah is deprecated and is no longer the recommended method of generating header files. Instead, you pass a -h flag to javac that tells it which directory to write the native headers to. So, using Java 8 or 9, you compile and produce the headers in a single step:

javac Sample1.java -h .

Regardless of the method used above, you will have two new files Sample1.class and Sample1.h.

Scala

With Scala, there’s currently no way to avoid using the deprecated javah tool. It’s a bit more involved than our previous example as javah may need to have the Scala libraries on it’s classpath.* In this example, we’ll just include the the scala-library.jar to illustrate how it’s done. I’m working on a Mac and installed Scala via homebrew. My classpath below reflects that. Your path will likely be different depending on your platform. To compile the Scala code and generate the header file do the following:

# Compile
scalac Sample1.scala
# javah needs access to scala-library.jar
LIBS_HOME=/usr/local/Cellar/scala/2.12.4/libexec/lib
CP=$LIBS_HOME/scala-library.jar
javah -cp $CP:. Sample1

If you did these steps correctly, you’ll have three new files: Sample1$.class, Sample1.class, and Sample1.h.

Output from javah/javac -h

Regardless of whether you used Java or Scala, Sample1.h will look exactly the same:

Step 3- Create a Native Implementation

For both our Scala and Java examples, the native implementation is exactly the same. You can write implementations in either C or C++, although the code will be different. The sample below implements all the methods defined in Sample1.h in C++. Copy and paste the code below into a file named Sample1.cpp:

Now the fun part, getting this to compile on your particular operating system. I’m working on a Mac, heres the command line blurb I use to compile Sample1.cpp to a shared native library:

g++ -dynamiclib -O3 \
-I/usr/include \
-I$JAVA_HOME/include \
-I$JAVA_HOME/include/darwin \
Sample1.cpp -o libSample1.dylib

A few notes about this:

  1. Notice the -dynamiclib flag, this creates a shared library and is required. On linux, use -shared instead.
  2. You have to include Java’s JNI headers which are always in at least 2 places:
  • $JAVA_HOME/include
  • $JAVA_HOME/include/<platform>. On Macs thats $JAVA_HOME/include/darwin, on Linux it’s $JAVA_HOME/include/linux. I have no idea what it is on Windows.

3. You may have to set the JAVA_HOME variable on your machine. You can do this on a Mac using:

4. Remember the line in our Java/Scala code that goes System.loadLibrary("Sample1")? Although the name of the native library is referenced using Sample1 in our code, it’s actually looked up using a lib prefix. So the native library is actually named libSample1.dylib on Mac or libSample1.so on Linux.

5. If you’re stuck developing for Java 6 on Mac (I’m so sorry for you), change the native libraries extension to .jnilib or Java 6 won’t find it. Java 7+ requires .dylib as the extension.

Step 4 -Running the Code

Running either sample is straight-forward. However, you need to ensure that native library is on Java’s or Scala’s library path. The simplest way to do this is to add it to the java.library.path when you start your JVM.

Java

# Assumes all files are in the same directory
java -cp . -Djava.library.path=$(pwd) Sample1

Scala

scala -cp . -Djava.library.path=$(pwd) Sample1

or

LIB_HOME=/usr/local/Cellar/scala/2.12.4/libexec/lib
CP=$LIB_HOME/scala-library.jar
java -cp $CP:. -Djava.library.path=$(pwd) Sample1

Footnotes

  • Our Scala example used here doesn’t require the scala-library as we didn’t use any Scala specific code. But most likely, you will need to include the Scala libraries on the classpath for real projects.
  • It’s not always feasible to set the java.library.path variable (e.g. when using Java Web Start). However, it is not particularly difficult to hack the path used internally by Java to add shared libraries at runtime. This is, however, a topic beyond the scope of this posting.
  • This article was original posted by myself on blogspot. I’ve updated it with current information and reposted it here.

--

--

Brian Schlining
Brian Schlining

Written by Brian Schlining

Polyglot coder. Deep-sea Researcher. Zazen aficionado. I think squids are pretty cool.

Responses (5)