Skip to content

Error Handling

Eugene Gershnik edited this page Oct 9, 2021 · 2 revisions

All errors in SimpleJNI are reported via exceptions. All exceptions thrown service from std::exception. If an error is a result of Java exception it will be "wrapped" in java_exception class. Note that SimpleJNI calls JNIEnv::ExceptionClear prior to emitting java_exception so you can freely use JNI calls while handling it. In your implementation of Java native methods as well as in JNI_OnLoad you must handle all C++ exceptions and never let them 'escape' into Java. Most Java implementations are not prepared to deal with C++ exceptions, even just letting them "fly through".

What to do with C++ exceptions on Java boundary?

Assuming you cannot actually handle the exception, as in "deal with error condition and retry", how do you handle C++ exceptions on Java boundary? One, often used, option is to log the error and report failure to Java via an error code of some sort (perhaps null return). This works but is not optimal. Error codes are easy to ignore and logs might not be seen. In addition this completely bypasses normal exception handling infrastructure Java code undoubtedly already has. A better idea is to propagate C++ exception into Java as Java exception and let Java code deals with it as it see fit. This is very easy if the C++ exception is java_exception, that is it originated as Java exception already. Just do

JNIEnv * env = ...;
...
catch(java_exception & ex)
{
    ex.raise(env);
}

And return default value to Java code (0 for integers, 0.0 for floats, nullptr for objects). Remember not to issue any JNI calls after call to raise. It signals Java exception and all subsequent JNI calls will fail. What to do with other C++ exceptions is up to you. The simplest strategy is to report them as Throwable with the message being the result of std::exception::what() call. This strategy is so common that SimpleJNI includes direct support for it. Just wrap all you native methods bodies in the following

some_type SomeNativeMethod(JNIEnv * env, ...other args...)
{
    try
    {
        ... method body ...
    }
    catch(std::exception & ex)
    {
        java_exception::translate(env, ex);
    }
    return some_type(0);
}

The java_exception::translate call will perform exactly the transformations described above: rethrow Java exception reported via java_exception and throw Throwable with message of C++ exception for anything else.

In a more complicated code base you might want to include more sophisticated exception translation machinery. You can use void java_exception::raise(JNIEnv * jenv, jthrowable ex) to raise any kind of jthrowable you create. One way of writing exception translation function is as follows

void translate_exception(JNIEnv * env, const std::exception & ex);

some_type SomeNativeMethod(JNIEnv * env, ...other args...)
{
    try
    {
        ... method body ...
    }
    catch(std::exception & ex)
    {
        translate_exception(env, ex);
    }
    return some_type(0);
}

void translate_exception(JNIEnv * env, const std::exception & ex)
{
    try
    {
        throw;
    }
    catch(java_exception & ex)
    {
        //Rethrow any Java exception
        ex.raise(env);
    }
    catch(my_exception & ex)
    {
        //Create jMyException from my_exception data 
        jMyException exception = ...;
        java_exception::raise(env, exception.c_ptr());
    }
    catch(std::exception & ex)
    {
        //Default case: transalte to throwable
        const char * message = ex.what();
        auto java_message = java_string::create(env, message);
        auto exception = java_runtime::throwable().ctor(env, java_message.c_ptr());
        java_exception::raise(env, exception.c_ptr());
    }
}