Catching Exceptions in Java

Software design must include error handling from the beginning, not as an afterthought. If the design includes declared "checked" exceptions, as in Java, then correcting any oversights may require painful refactoring. There is no guarantee new exceptions can be handled properly without introducing major contradictions in the code. Only extensive testing can uncover all possible exceptional conditions. We do not want to make this chore more painful than necessary.

You catch a specific checked exception (such as an "IOException") because you know how to work around the problem. If not, your method declares the exception to be thrown to a client who might know how to work around it. Sometimes you do both because you can clean up part of the mess before rethrowing. Sometimes you catch a specific "FooException" and rethrow as a specific "BarException" more appropriate to the context.

These seem like easy enough rules to follow. Right?

Most everyone knows better than to catch, log, and then simply ignore an exception. An error has not been handled just because it was documented. No one looks at logs of stack traces until after a program has crashed for other reasons.

But there are some implied rules we must also follow:

  1. Avoid catching a generic "Exception," "RuntimeException," or "Throwable." A generic exception can arise from too many sources. If you must catch a generic exception in a context that does not allow any exceptions, then arrange to have it rethrown elsewhere. Otherwise, you might as well terminate the process with a useful fatal error message.
  2. Do not simply wrap an exception and rethrow as a generic "RuntimeException", unless your intention is to crash the process. A checked exception must be declared for clients, or it must be fixed in the handler. There are no safe alternatives.

Unchecked RuntimeExceptions are expected to be avoidable. The programmer failed to foresee a common situation, so the remaining logic is likely to be flawed. These bugs need to be uncovered as soon as possible in tests. Think through the possibilities: An ArrayIndexOutOfBoundsException could have been avoided by checking the index first. An object should be checked with instanceof to avoid a ClassCastException. And so on for NullPointerException and IllegalArgumentException. Does your exception belong to this category? Even if it does, you still want to throw a specific unchecked exception. No one inspects the causes of generic exceptions.

If you throw a generic exception, then someone must catch it. Why is catching a generic exception so troublesome? You may think you know the cause, but there are always more obscure causes possible. An ignored InterruptedException might prevent your process from shutting down properly. What if it is a NullPointerException from an uninitialized member variable? Then you have just caught and hidden a bug. Buggy race conditions in threaded code may fail only occasionally. Data-dependent errors may only show up during beta testing.

GUI Listeners or threaded Runnables often implement methods that cannot declare exceptions. This is probably the single most common location for caught and ignored exceptions, and one of the most dangerous. In this case, you can catch a specific FooException, save it, then handle it from another thread.

When running client-implemented code, you might catch specific RuntimeExceptions, such as an ArithmeticException, if you do think you know how to recover from the problem. Just avoid catching generic exceptions that could be caused by a general problem with the process. It is still better to crash early and often than to let a client remain unaware of an error that can be fixed. Generic RuntimeExceptions should only be caught at the highest level of the program, as in a ThreadGroup#uncaughtException, and then only for cleanup and useful logging.

If you inherit code that already hides too many exceptions, then expect to go through a painful refactoring, adding declarations and deciding how far they must go before they can be handled correctly. Let them go all the way to main() before you let yourself catch and ignore one. Eventually you will decide what must be done. Let your unhandled exceptions remind you.

Let us review. Why is the following code such a problem?
public Foo getFoo() {
  try {
    return fooSource.getFoo();
  } catch (Exception e) {
    return null;  
  }
}

You might have seen a MissingResourceException or NoSuchElementException, so you felt a null result would be just as informative. What if you accidentally caught and hid a ConcurrentModificationException or a SQLException? Those could be bugs that left your application in a broken state. Your client might only see odd side-effects, or a crash five minutes later. How are you going to debug that?

(You may disagree philosophically with the intention of declared exceptions and prefer all exceptions to be unchecked and undeclared like RuntimeExceptions. Too bad. If you use only RuntimeExceptions, you are inventing a non-standard Java style that will not integrate well with others. You will force clients to catch all passing RuntimeExceptions and examine their source. http://java.sun.com/docs/books/tutorial/essential/exceptions/runtime.html )

Here is a suggestion for stabilizing an application that already has poor error handling: [ Stabilizing_a_large_interactive_Java_application.html ] .

Bill Harlan, Feb 2009


Return to parent directory.