Let Exceptions Propagate Only As Unchecked Exceptions

Consider the compare method of java.util.Comparator:
  int compare(T o1, T o2) throws ClassCastException 

What do you do if you implement Comparator, and you have an SQLException whilst performing the comparison? You cannot alter the signature of the Comparator interface's compare method. You are forced to either swallow the exception, or wrap it as a RuntimeException.

Clearly, traditional advice for using checked exceptions falls apart when you are required to write code to play nicely with other people's interfaces.

An ExceptionStrategy? that addresses these issues and affords us ConsistencyOfApproach? is described below.

Thus, our strategy is:

   Class A {

void methodA(...) throws SpecificCheckedException {

/* * if we can throw an exception that the caller may * be able to act intelligently on, then do so */

if(...) throw new SpecificCheckedException();

try { doDatabaseThing(); }

catch(SQLException e) {

/* * if we need to simply indicate failure, then rethrow * it as a RuntimeException. The ExceptionAdapter ensures * that we don't wrap any exceptions that are already unchecked. */ throw ExceptionAdapter.toRuntimeException(e); } } }

(See ExceptionAdapter)

Summary: when writing a method:

If a method that you call throws a checked exception, *always* catch it, and then either

OR OR Note: this means that you *always* catch all checked exceptions thrown by any methods you call, and *never* let them 'pass through' your method because by luck the exceptions you have declared that you throw yourself happen to be the same as or a superset of the exceptions thrown by the methods you call.

The whole reason that we want to let unchecked exceptions indicating general failures that we cannot act intelligently on propagate is so that we can abort an operation, abort and rollback a transaction, or log the issue at an appropriate level.

Although this means we're not *forced* to try and catch unchecked exceptions by the compiler and we think we might forget, that's the same problem we face if a NullPointerException or other non-checked exception propagates up. We simply need to always remember to catch all checked and unchecked exceptions at the operation/transactional level as appropriate - which would be true whether or not we adopted this entire strategy and because we never know when a NullPointerException or RuntimeException generated by code outside of our control may come along and break our operation/transaction.

-- AndreParrie

I HaveThisPattern -- PhilippeDetournay

Further notes

Another variation on this approach is, when creating your own exceptions, to always have them as subclasses of RuntimeException. Then we need not worry about having to wrap our own checked exceptions as RuntimeExceptions? to let them propagate, and only need to worry about wrapping other people's checked exceptions. This results in cleaner code. As in C++, it is the responsibility of the programmer using your methods to check your javadoc for a list of exceptions that may be thrown. The thinking behind this approach is that although the programmer using such a method will not be forced to consider each exception that could be thrown, in many if not most cases they will not want to do anything with your exceptions other than to let them propagate. Sun understood this when they made the NullPointer exception a RuntimeException, because it would create too much messy and unnecessary error handling code. We simply apply the same principle here.

Consider the propagation of exceptions generated within servlets. In order to propagate, they must either be wrapped inside a ServletException? or must be a RuntimeException. The ServletException? class adds no value. If the pattern of creating no-added-value exception wrappers were to be followed every time we needed a checked exception to propagate, every package we write would have its own exception wrapper, and exceptions could be wrapped and rewrapped dozens of times while 'bubbling' to the top.

Have you considered refactoring your code such that a simple comparator could never throw a SQLException? Seriously, while Exception propagation issues are real, I don't find them to be as strange as this. I would never put database code inside a comparator. Perhaps your issues with checked Exceptions are telling you something about your programming style.
Reply: The SQLException example was encountered when using a 3rd party persistence library which supported lazy loading of persistent object fields. Even though optimized code would ensure that those fields being compared were pre-loaded so that no actual additional database access would be required, the method call to retrieve the field still is declared as being capable of throwing one of several data access related exceptions. To assume that the pre-loading was successful and swallow all of these exceptions just to fit with the Comparator interface would be very bad practice, in my opinion.

Good code will always try to allow exceptions to propagate as much as possible, as opposed to 'swallowing' them. I have encountered endless scenarios where I've needed exceptions to propagate even though the interfaces I'm writing code to adhere to does not allow me to throw them. This is a particularly acute problem when the exceptions I may need to propagate are implementation specific, meaning that it would be incorrect for the interface I'm implementing to allow me to throw those implementation specific exceptions.

The best way in my opinion to allow an implementation specific exception to propagate upward is to make it unchecked. The alternatives are to HomogenizeExceptions (which only works within a particar package/module and not between them) or to use the same pattern that Sun used with the ServletException? wrapper, which I think results in unnecessary extra code.

Or even more evil. I would have thrown the SQLException via EvilThrow?.ThrowThrowable?.

See CheckedExceptionsAreOfDubiousValue JavaExceptionsAreParticularlyEvil ThrowDontCatch
CategoryException CategoryJava

View edit of March 19, 2008 or FindPage with title or text search