Java Language Pitfall - Throwing Throwable, Exception, Error or RuntimeException


Example

While catching the Throwable, Exception, Error and RuntimeException exceptions is bad, throwing them is even worse.

The basic problem is that when your application needs to handle exceptions, the presence of the top level exceptions make it hard to discriminate between different error conditions. For example

try {
    InputStream is = new FileInputStream(someFile);  // could throw IOException
    ...
    if (somethingBad) {
        throw new Exception();  // WRONG
    }
} catch (IOException ex) {
    System.err.println("cannot open ...");
} catch (Exception ex) {
    System.err.println("something bad happened");  // WRONG
}

The problem is that because we threw an Exception instance, we are forced to catch it. However as described in another example, catching Exception is bad. In this situation, it becomes difficult to discriminate between the "expected" case of an Exception that gets thrown if somethingBad is true, and the unexpected case where we actually catch an unchecked exception such as NullPointerException.

If the top-level exception is allowed to propagate, we run into other problems:

  • We now have to remember all of the different reasons that we threw the top-level, and discriminate / handle them.
  • In the case of Exception and Throwable we also need to add these exceptions to the throws clause of methods if we want the exception to propagate. This is problematic, as described below.

In short, don't throw these exceptions. Throw a more specific exception that more closely describes the "exceptional event" that has happened. If you need to, define and use a custom exception class.

Declaring Throwable or Exception in a method's "throws" is problematic.

It is tempting to replace a long list of thrown exceptions in a method's throws clause with Exception or even `Throwable. This is a bad idea:

  1. It forces the caller to handle (or propagate) Exception.
  2. We can no longer rely on the compiler to tell us about specific checked exceptions that need to be handled.
  3. Handling Exception properly is difficult. It is hard to know what actual exceptions may be caught, and if you don't know what could be caught, it is hard to know what recovery strategy is appropriate.
  4. Handling Throwable is even harder, since now you also have to cope with potential failures that should never be recovered from.

This advice means that certain other patterns should be avoided. For example:

try {
    doSomething();
} catch (Exception ex) {
    report(ex);
    throw ex;
}

The above attempts to log all exceptions as they pass, without definitively handling them. Unfortunately, prior to Java 7, the throw ex; statement caused the compiler to think that any Exception could be thrown. That could force you to declare the enclosing method as throws Exception. From Java 7 onwards, the compiler knows that the set of exceptions that could be (re-thrown) there is smaller.