: code tutorial (v3)

Lesson 10: Handling Exceptions

Introduction

Error handling is a complex field. A native process that calls Java methods and accesses native fields has to deal with the exceptions that might be thrown by these activities.

The quality of a wrapper API is directly dependent on the quality of its error handling. Codemesh's products support a variety of errorhandling modes that give you complete flexibility in how you write your application.

You usually go with the default error handling policy or set it globally for your entire application when you configure the JunC++ion runtime at the start of your application. Please see the runtime configuration tutorial for more details.

Typed Exceptions

The default error handling policy is called TypedException. Using this policy, a proxy type exception is thrown whenever a Java exception occurred. When you use this policy, the runtime will search for the best matching proxy exception type that is available. This match will not always be exact. Many Java operations can throw so-called unchecked exceptions, i.e. exceptions of a type derived from java.lang.RuntimeException.

Very often, you don't have a proxy type for the exact subclass of RuntimeException that was thrown by a Java call. In this case, the runtime library will search for the best possible type that is available. It will traverse the inheritance chain all the way to the proxy type for java.lang.Throwable. It will pick the proxy type that is closest to the exact exception type in the inheritance chain. This means that the following code will work even if you do not have a proxy type for ArrayIndexOutOfBoundsException, as long as you have one for RuntimeException:

try
{
    // create an integer array with 10 elements
    xmog_java_int_array   arrInt( 10 );

    // try accessing the 12th element in the array, causing
    // an ArrayIndexOutOfBoundsException to be thrown in the JVM
    jint   temp = arrInt[ 12 ];
}
catch( java::lang::RuntimeException & rte )
{
    cout << rte.getMessage() << endl;
}

To be on the safe side, you might always want to add a catch block for type xmog_exception_impl after your typed exception catch blocks. While a correctly deployed application should not need them, it might allow you to catch some misconfigurations that would otherwise simply cause your application to crash.

Please note that exceptions should always be caught by reference (using the ampersand symbol in the catch block) and never thrown by pointer. You could also catch them by value (without the ampersand) but that would only catch the exact exception type and not derived exception types. Catching by reference is the better choice. Please resist the temptation to throw C++ proxy exceptions the same way you would do it in Java, namely using the keyword new.

Never do the following:

try
{
    // do NOT do this
    throw new RuntimeException( "Something didn't work out" );
}
catch( java::lang::RuntimeException & rte )
{
    cout << rte.getMessage() << endl;
}

Why is that problematic? Because you are throwing a pointer to a RuntimeException and you're trying to catch a reference to RuntimeException. The type being thrown does not match the type being caught and consequently the catch block will not be executed. Likely your application will crash too, unless you have a catch-all somewhere else. What's more, you're leaking a pointer and a Java exception instance unless you manually delete the thrown exception somewhere.

Also, the runtime framework throws exception instances (designed to be caught by reference), which means that you would need different catch blocks for the exceptions that you throw manually by pointer and exceptions that the framework throws in reaction to an exception happening on on the Java side.

Generic Exception

What would happen if you had no matching exception types at all? In that case you should probably pick a different error handling policy because clearly you are not interested in catching strongly typed proxy exception types. That's what the GenericException policy is for.

That policy converts all Java exceptions into xmog_exception_implcode> instances. This gives you basic errorhandling capabilities without having to generate a lot of proxy exception types. This policy is useful if you need to minimize your proxy set and eliminiating all exception proxy types is a convenient way to do that without losing too much functionality. The example from the TypedException section above would look like this if we're using the GenericException policy:

try
{
    // create an integer array with 10 elements
    xmog_java_int_array   arrInt( 10 );

    // try accessing the 12th element in the array, causing
    // an ArrayIndexOutOfBoundsException to be thrown in the JVM
    jint   temp = arrInt[ 12 ];
}
catch( xmog_exception_impl & rte )
{
    // extract the exception message using framework API methods
    xmog_base   msg( rte.getMessage(), xmog_base::LOCAL );
    const char* strMsg = xmog_java_string::to_char( msg.get_jobject_() );

    cout << strMsg << endl;

    xmog_java_string::free( msg, strMsg );
}

Configuration

The error handling policy can be set prior to starting the JVM by using the xmog_jvm_loader class, in which case the entire framework will use your specified error handling policy from the moment the JVM is loaded. Please note at this point that we're not configuring what the JVM does in the case of errors or exceptions; the error handling policy only governs how JVM error conditions are handled on the C++ side. You can also change the error handling policy for a loaded xmog_jvm instance and even for a specific thread via the xmog_localenv type. This can be useful if you want to temporarily override the error handling policy for a specifc activity without affecting error handling for the entire application. To summarize: