Monday, January 26, 2009

A Java Exception Chaining Reminder

Java 1.4 introduced so many highly useful features that many projects were slow to migrate to J2SE 5 or Java SE 6. Even with all of these great new features, the simple exception chaining mechanism stands out as one of those small features that benefits me in my Java development (and especially maintenance) work on a near-daily basis. Many of us were implementing custom exception chaining work-arounds before this, but the standardized approach was highly welcome.

Although Java exception chaining is simple to apply, this does not mean it is applied uniformly. In his blog post A Pox Upon All of Your getMessage Calls, Ian Darwin (author of Java Cookbook) states that he has run across several Java-based web frameworks that do not provide sufficient exception information in wrapped exceptions. He also points out the superior information provided by passing a causal exception's toString()-based String to the constructor of the wrapping exception rather than passing the causal exception's getMessage()-based String.

Joshua Bloch devotes an entire chapter of Effective Java (Chapter 9 in the Second Edition) to discussion of effective practices related to Java exceptions. Bloch points out the problems associated with ignoring exceptions (item #65 in Second Edition) and recommends throwing exceptions that are appropriate for the particular level of abstraction. With these two concepts in mind, I want to move on to some Java code examples demonstrating the various approaches to re-throwing a new exception based on a causal exception.

For my admittedly contrived example, I will be using a custom exception called UninstantiableClassException. Its source data is shown next:

UninstantiableClassException.java


package dustin;

/**
* Exception class indicating situation in which a class cannot be instantiated.
*/
public class UninstantiableClassException extends RuntimeException
{
/**
* Constructor for this exception.
*/
public UninstantiableClassException(final String newExceptionMessage)
{
super(newExceptionMessage);
}

/**
* Constructor accepting an initial cause.
*/
public UninstantiableClassException(final Throwable cause)
{
super("Provided class cannot be instantiated.", cause);
}
}


The exception defined above has two constructors. One accepts a String argument and one accepts a Throwable. The constructor accepting a Throwable will be used to allow this constructor to completely wrap/chain a causal exception. The other constructor, the one accepting a String, will allow this new exception to be based on any provided arbitrary String.


The AbstractClass that cannot be instantiated is shown next:

AbstractClass.java


package dustin;

public abstract class AbstractClass
{
/** Some integer. */
private int someInt;
}



Finally, the code that executes the three examples is shown next.

ExceptionExamples.java


package dustin;

import java.lang.InstantiationException;

/**
* Class for demonstrating comparison of use of {@code toString()} versus
* {@code getMessage()} in exception translation.
*/
public class ExceptionExamples
{
/**
* Throw nested exceptions with getMessage().
*
* @throws UninstantiableClassException Thrown if class cannot be instantiated.
*/
public static void throwNestedExceptionsWithGetMessage()
{
try
{
final AbstractClass instance = AbstractClass.class.newInstance();
}
catch (InstantiationException instantiationEx)
{
throw new UninstantiableClassException(instantiationEx.getMessage());
}
catch (IllegalAccessException illegalAccessEx)
{
throw new UninstantiableClassException(illegalAccessEx.getMessage());
}
}

/**
* Throw nested exceptions with toString().
*
* @throws UninstantiableClassException Thrown if class cannot be instantiated..
*/
public static void throwNestedExceptionsWithToString()
{
try
{
final AbstractClass instance = AbstractClass.class.newInstance();
}
catch (InstantiationException instantiationEx)
{
throw new UninstantiableClassException(instantiationEx.toString());
}
catch (IllegalAccessException illegalAccessEx)
{
throw new UninstantiableClassException(illegalAccessEx.toString());
}
}

/**
* Throw nested exceptions with exception chaining mechanism.
*
* @throws Illeg
*/
public static void throwNestedExceptionsWithChainedException()
{
try
{
final AbstractClass instance = AbstractClass.class.newInstance();
}
catch (InstantiationException instantiationEx)
{
throw new UninstantiableClassException(instantiationEx);
}
catch (IllegalAccessException illegalAccessEx)
{
throw new UninstantiableClassException(illegalAccessEx);
}
}

/**
* Main function for executing exception examples.
*
* @param arguments Command-line arguments; lack of any argument indicates
* use of the {@code getMessage()} approach, an argument "tostring"
* indicates use of the {@code toString()} approach, and any argument
* other than "tostring" indicates use of exception chaining mechanism.
*/
public static void main(final String[] arguments)
{
if (arguments.length < 1)
{
throwNestedExceptionsWithGetMessage();
}
else if ("TOSTRING".equalsIgnoreCase(arguments[0]))
{
throwNestedExceptionsWithToString();
}
else
{
throwNestedExceptionsWithChainedException();
}
}
}



The main function above executes three methods defined to catch an intentionally thrown checked exception InstantiationException and re-throw it as the unchecked exception UninstantiableClassException defined above.

Each of the three methods constructs the re-thrown exception in a different manner. One approach shown is to use actual exception chaining and provide the causal exception (InstantiationException) as an argument to the constructor of the new exception. The other two approaches both rely on the String-argument constructor of the new exception. Because these approaches don't have access to the original exception, significantly less detail is available in the new exception than that which is available when the original causal exception is provided.

There may be times when one does not want to provide the causal exception to the new exception, but still wants to retain at least the name of the original exception in the next exception. This is where it is important to distinguish between getMessage() and toString. The API documentation for Throwable.toString() clearly indicates that toString prepends the name of the particular exception class to the message associated with the exception. Therefore, as discussed in Darwin's blog post mentioned previously, it seems that a developer should always use toString() rather than getMessage() when providing a String for construction of another (wrapper) exception. Even better yet, it is probably a good idea to generally prefer providing the causal exception itself to the new exception unless there is a reason not to do so.

The screen snapshot shown next shows the differences in results for the three approaches demonstrated by the code above. The approach using full exception chaining provides by far the most detail with a complete stack trace. The two String-based approaches show a similar level of detail with the one significant difference being that the use of toString() on the causal exception rather than getMessage() led to the inclusion of the causal exception in the message of the wrapping exception.





Conclusion

The information covered in this blog posting contains nothing new because Java exception chaining was introduced with Java 1.4. However, I have seen enough cases where exception chaining is not used to its full capabilities to think that it is worth a reminder post about the merits and simplicity of exception chaining.

My first choice is to use true and full exception chaining by passing the causal exception itself into the constructor of the new, wrapping exception whenever possible. When that is not desired for some reason (such as the wrapping exception is out of one's control and only has a String constructor1), then it is beneficial (without any extra effort) to provide the causal exception's message AND name via its toString() method rather than providing only its message (via the getMessage() method).


1The method Throwable.initCause(Throwable) actually can also be used to set the causal Throwable if the wrapping exception does not provide a constructor for this purpose.

No comments: