Nice JNI Exceptions

Nice JNI Exceptions

JNI, or Java Native Interface[a], is the way Java code calls non-Java code, and in particular C and C++ code. Writing C and C++ code usually means using error codes instead of exceptions, and unless we do something about it, we get code that looks like this:

public static final int RESULT_SOME_ERROR = -1;
public static native int nativeDoStuff ();
public static void doStuff () {
    int res = doStuff ();
    switch (res) {
    case RESULT_SOME_ERROR: throw new SomeError ();

There is much that is wrong with that. First, it forces us to handle the error in two places - once in the native code, and then once in Java. Second, as Yossi Kreinin writes in Error codes vs exceptions: critical code vs typical code[b], we lose information about the error - the SomeError instance has no information beyond its type.

1. Introduction

What we will do is look at how to throw Java exceptions from C++ code and capture the line number and function name. The end result will look something like this:

Exception in thread "main" gphoto2.GPhoto2Exception: No camera auto detected. (-105: GP_ERROR_MODEL_NOT_FOUND: Model not found)
 at <native>.Java_gphoto2_GPhoto2_beginSession0(GPhoto2.cpp:70)
 at gphoto2.GPhoto2.beginSession0(Native Method)
 at gphoto2.GPhoto2.beginSession(
 at gphoto2.test.GPhoto2Test.main(

2. Exception Base Class

We start by creating a base class for Exceptions, with a method, __jni_setLocation that the native code can use to set the location in the native code where the exception was thrown:

package util;

 * Base class for Exceptions that are throwable from code 
 * generated by the JNIThrowableGenerator. JNIRuntimeException
 * and JNIError base classes can be created the same way.
public class JNIException extends Exception {
    public JNIException (String message) {
        super (message);
     * Called by native code during construction to set the location. 
    public void __jni_setLocation (String functionName, String file, int line) {
        JNIExceptions.addStackTraceElement (this, functionName, file, line);

2.1. Common Code

Since we'll want not just a base class for Exceptions, but also for RuntimeExceptions and Errors, we factor out the code that pushes the native location onto the stack trace to a common helper class:

package util;

 * Support routines for the JNI exception classes.
public class JNIExceptions {
     * Pushes a stack trace element onto the existing stack trace of the throwable. 
    public static void addStackTraceElement (
        Throwable t, 
        String functionName, 
        String file, 
        int line) {
        StackTraceElement[] currentStack = 
            t.getStackTrace ();
        StackTraceElement[] newStack = 
            new StackTraceElement[currentStack.length + 1];
        System.arraycopy (currentStack, 0, 
            newStack, 1, currentStack.length);
        file = file.replace ('\\', '/');
        if (file.lastIndexOf ('/') > -1) {
            file = file.substring (file.lastIndexOf ('/') + 1);
        newStack[0] = new StackTraceElement ("<native>", functionName, file, line);
        t.setStackTrace (newStack);

3. The Exception Class

Then we can use the base class to create our GPhoto2 exception class:

package gphoto2;

import java.util.Map;
import java.util.HashMap;
import util.JNIException;

 * GPhoto2 error code.
public class GPhoto2Exception extends JNIException {
    private static final Map<Integer,String> errorCodes = new HashMap<> ();
    private static void addErrorCode (int code, String def, String msg) {
        errorCodes.put (code, def + ": " + msg);
    static {
        addErrorCode (0, "GP_OK", "Everything is OK.");
        addErrorCode (-1, "GP_ERROR", "Generic Error.");
        /* ... */
    public GPhoto2Exception (int code, String message) {
        super (message + " (" + code + ": " + errorCodes.get (code) + ")");

4. C++ Exception Throw Code

The final step is to create the function that the naitve code will use to throw the exception. With an eye toward using a code generator to create this based on the Java Exception class, I'll try to indicate which parts are fixed and which would vary with the exception class. First, the implementation:

4.1. Implementation

throwGPhoto2Exception (
    // The first four parameters are common for all
    // throwables
    JNIEnv* env, 
    const char* functionName, 
    const char* file, 
    const int line,
    // These are specific to the GPhoto2Exception
    const int code,
    const char* message
    // Find and instantiate a GPhoto2Exception
    jclass exClass = 
        env->FindClass ("gphoto2/GPhoto2Exception");
    jmethodID constructor = 
        env->GetMethodID (exClass, "<init>", 
    jobject exception = env->NewObject (exClass, constructor,
        env->NewStringUTF (message));

    // Find the __jni_setLocation method and call it with 
    // the function name, file and line parameters
    jmethodID setLocation = 
        env->GetMethodID (exClass, "__jni_setLocation", 
    env->CallVoidMethod (exception, setLocation, 
        env->NewStringUTF (functionName),
        env->NewStringUTF (file),

    // Throw the exception. Since this is native code,
    // execution continues, and the execution will be abruptly
    // interrupted at the point in time when we return to the VM. 
    // The calling code will perform the early return back to Java code.
    env->Throw ((jthrowable) exception);
    // Clean up local reference
    env->DeleteLocalRef (exClass);

4.2. Header

Then we need a header file. Nothing strange here:

#ifndef gphoto2_GPhoto2Exception__JNIThrow
#define gphoto2_GPhoto2Exception__JNIThrow
 * Throws a GPhoto2Exception. Use the macro 
 * ThrowGPhoto2Exception(code, message) to
 * call this function.
throwGPhoto2Exception (
    JNIEnv* env, 
    const char* functionName, 
    const char* file, 
    const int line,
    const int code,
    const char* message
 * Throws a GPhoto2Exception. The macro assumes that the current scope has a
 * symbol named env that is of the type JNIEnv*.
#define ThrowGPhoto2Exception(code, message) throwGPhoto2Exception (env, __func__, __FILE__, __LINE__, code, message)


You may want to replace the use of __func__ with __PRETTY_FUNCTION__ if you use GCC and thus have access to GCC's magic variables[c].

5. Usage

The usage in native code is quite straightforward:

Java_gphoto2_GPhoto2_beginSession0 (
    JNIEnv* env,
    // ...
    gp_camera_new (&camera);
    context = gp_context_new();
    // set callbacks for camera messages
    gp_context_set_error_func(context, error_func, NULL);
    gp_context_set_message_func(context, message_func, NULL);
    // This call will autodetect cameras, take the first one from the list and use it
    int ret = gp_camera_init(camera, context);
    if (ret != GP_OK) {
        // Clean up
        // !!! Throw an exception and return !!!
        ThrowGPhoto2Exception (ret, "No camera auto detected.");
        // !!! Throw an exception and return !!!
        return ret;
    return 0;

The corresponding Java code is also fairly straightforward:

protected static native int beginSession0 () throws GPhoto2Exception;
public synchronized static void beginSession () throws GPhoto2Exception {
    // ... some code omitted ...
    beginSession0 ();