How to catch JNI/Java Exception

I have a JNI layer in my application. In some cases Java throws an exception. How can I get the Java exception in the JNI layer? I have the code something like as follows.

if((*(pConnDA->penv))->ExceptionCheck(pConnDA->penv))
{
    (*(pConnDA->penv))->ExceptionDescribe(pConnDA->penv); 
    (*(pConnDA->penv))->ExceptionClear(pConnDA->penv);
}

Will this block of code catch only JNI exceptions? Where will the exception description be logged in console(stderr)? How do I get this into the buffer, so that I can pass it to my logger module?


Solution 1:

if you invoke a Java method from JNI, calling ExceptionCheck afterwards will return JNI_TRUE if an exception was thrown by the Java.

if you're just invoking a JNI function (such as FindClass), ExceptionCheck will tell you if that failed in a way that leaves a pending exception (as FindClass will do on error).

ExceptionDescribe outputs to stderr. there's no convenient way to make it go anywhere else, but ExceptionOccurred gives you a jthrowable if you want to play about with it, or you could just let it go up to Java and handle it there. that's the usual style:

jclass c = env->FindClass("class/does/not/Exist");
if (env->ExceptionCheck()) {
  return;
}
// otherwise do something with 'c'...

note that it doesn't matter what value you return; the calling Java code will never see it --- it'll see the pending exception instead.

Solution 2:

This is a complement of the Elliott Hughes' answer. My answer provides a step-by-step example explaining how to catch exceptions and how to translate them between C++ and Java words using the JNI layer.

Short answer

See the correct Elliott Hughes' answer.

Reusable example

This answer and snippets are in public domain or in CC0 in order to ease reuse. All the source code here is C++03 backward compatible.

To reuse the above snippet do the following:

  • Replace mypackage::Exception by your own C++ Exception.
  • If the corresponding Java exception my.group.mypackage.Exception is not defined, then replace "my/group/mypackage/Exception" by "java/lang/RuntimeException".

Catch exceptions from Java

See also the snippet on coliru.

void rethrow_cpp_exception_as_java_exception()
{
  try
  {
    throw; // This allows to determine the type of the exception
  }
  catch (const mypackage::Exception& e) {
    jclass jc = env->FindClass("my/group/mypackage/Exception");
    if(jc) env->ThrowNew (jc, e.what());
    /* if null => NoClassDefFoundError already thrown */
  }
  catch (const std::bad_alloc& e) {
    jclass jc = env->FindClass("java/lang/OutOfMemoryError");
    if(jc) env->ThrowNew (jc, e.what());
  }
  catch (const std::ios_base::failure& e) {
    jclass jc = env->FindClass("java/io/IOException");
    if(jc) env->ThrowNew (jc, e.what());                          
  }                                                               
  catch (const std::exception& e) {
    /* unknown exception (may derive from std::exception) */
    jclass jc = env->FindClass("java/lang/Error");
    if(jc) env->ThrowNew (jc, e.what());
  }
  catch (...) {
    /* Oops I missed identifying this exception! */
    jclass jc = env->FindClass("java/lang/Error");
    if(jc) env->ThrowNew (jc, "Unidentified exception => "
      "Improve rethrow_cpp_exception_as_java_exception()" );
  }
}

I thanks Mooing Duck for contribution on the above C++ code.

Adapt the JNI generated source code

The following file Java_my_group_mypackage_example.cpp use the above rethrow_cpp_exception_as_java_exception() function:

JNIEXPORT jlong JNICALL Java_my_group_mypackage_example_function1
        (JNIEnv *env, jobject object, jlong value)
{
  try {
    /* ... my processing ... */
    return jlong(result);
  } catch(...) {
    rethrow_cpp_exception_as_java_exception();
    return 0;
  }
}

JNIEXPORT jstring JNICALL Java_my_group_mypackage_example_function2
        (JNIEnv *env, jobject object, jlong value)
{
  jstring jstr = 0
  try {
    /* ... my processing ... */
    jstr = env->NewStringUTF("my result");
  } catch(...) {
    rethrow_cpp_exception_as_java_exception();
  }
  return  jstr;
}

JNIEXPORT void JNICALL Java_my_group_mypackage_example_function3
        (JNIEnv *env, jobject object, jlong value)
{
  try {
    /* ... my processing ... */
  } catch(...) {
    rethrow_cpp_exception_as_java_exception();
  }
}

Corresponding Java code

File example.java

package my.group.mypackage;

public class Example {
  static {
    System.loadLibrary("my-DLL-name");
  }

  public Example() {
    /* ... */
  }

  private native int    function1(int); //declare DLL functions
  private native String function2(int); //using the keyword
  private native void   function3(int); //'native'

  public void dosomething(int value) {
    int result = function1(value);  
    String str = function2(value);  //call your DLL functions
    function3(value);               //as any other java function
  }
}

Note: "my-DLL-name" refers to the dynamic library produced from the above C/C++ code compiled. It can be my-DLL-name.dll on Windows or my-DLL-name.so on GNU/Linux/Unix.

Steps

  1. Generate example.class from example.java (using javac or maven or your favorite IDE Eclipse/Netbeans/IntelliJ IDEA/...)

  2. Generate C/C++ header file Java_my_group_mypackage_example.h from example.class using javah