Friday, July 24, 2009

Best Practice: Catching and re-throwing Java Exceptions

Question
What is the correct Java™ programming best practice to catch, print, and re-throw Java exceptions?

Cause
Problem determination is often hampered by mysterious errors, misleading information, or missing stack traces.

Answer
It is a well-known best practice that a Java application should not suppress caught exceptions with blank catch blocks; however, there are more subtle mistakes that Java applications make which can hamper problem determination. Here are 3 malpractices:


// #1. Worst -- there is no indication that an exception
// occurred and processing continues.
try {
// do work
} catch (Throwable t) {
}

// #2. Very Bad -- there is an indication that an
// exception occurred but there is no stack trace, and
// processing continues.
try {
// do work
} catch (Throwable t) {
System.err.println("There was a problem " + t.getMessage());
}

// #3. Incorrect. The stack trace of the original
// exception is lost. In the case of an Exception such as
// a NullPointerException, getMessage() will return a
// blank string, so there will be little indication of
// the problem.
try {
// do work
} catch (Throwable t) {
throw new ServletException("AUDIT ABC: " + t.getMessage());
}

The problem with #3 is that the ServletException will be shown in SystemOut.log but the stack trace and message will simply point to the ServletException which was created within the catch block. The true root problem is the caught exception, t, which has been lost because of a lack of a call to t.printStackTrace().

The correct way to catch and re-throw an exception is to pass the caught exception object as the "rootCause" or inner exception parameter to the constructor of the new exception (note that not all exception constructors support inner exceptions, in which case such an exception should not be used in such a case). When the exception is later caught and printed to SystemOut.log, the inner exception will be included:



// #4. Correct.
try {
// do work
} catch (Throwable t) {
throw new ServletException("AUDIT ABC: " + t.getMessage(), t);
}

// #5. correct.
try {
// do work
} catch (Throwable t) {
try {
// Perform some application logging or auditing
} catch (Throwable tAppDebug) {
tAppDebug.printStackTrace();
throw t;
}
}

Customers often have general catch blocks in Servlets, MDBs, EJBs and other core components where they catch all un-handled exceptions and re-throw them as new Exceptions, adding application specific debugging information or auditing information. Exception handling malpractices such as those described above have been a source of many major customer outages.

Finally, there is a case where a developer is "stuck" catching an exception that cannot be re-thrown (For example: "throw t") because the method signature does not allow it, such as a restricted list of checked exceptions. In this case, a developer may throw a runtime exception, which is unchecked, although it should be clear that this should be the last option used. This is a hack around the underpinnings of Java's exception model, and it should have a proper fix through architectural changes of the code:



// #6. A hack but it is better than suppression.
try {
// do work
} catch (Throwable t) {
throw new Error(t);
}