Skip to content

Java Exceptions Gotcha

Sean Kelly edited this page Mar 29, 2021 · 1 revision

The Java Programming Language features static signature checking that includes the exceptional (error) conditions that can arrise as a result of calling a method. However, many developers tend to engage in some somewhat unsafe practices when it comes to exceptions. This little essay might just help you avoid those little potholes and pitfalls.

๐Ÿค” Kinds of Throwable Objects

Exceptions in Java are just "throwable" objects, and there are two kinds:

  • Checked exceptions
  • Unchecked exceptions

๐Ÿ Checked Exceptions

Checked exceptions are exceptional (that is, not normal) conditions in a program, but usually recoverable conditions, such as:

  • File not found
  • Unknown host name

They're often the result of user interaction:

  • File not found? The filename was mistyped.
  • Unknown host name? DNS hasn't updated with the new host name.

In Java, checked exceptions must be handled. There are two ways to handle a checked exception:

  • By catching the exception and taking some kind of recovery action (in the catch part of a try statement)
  • Or by declaring that your method potentially throws the class of the checked exception ("passing the buck")

The compiler enforces the handling of checked exceptions by generating compile time errors. In Java, you always know what to expect when you call a method, which is kind of nice ๐Ÿ˜Œ

๐Ÿ˜’ Unchecked Exceptions

Unchecked exceptions are also exceptionalโ€”not normalโ€”conditions during the execution of a program. But they usually indicate a fault in the program or its environment. Some examples are:

  • Division by zero (fault in the program)
  • Out of memory (fault in the environment)

How do you handle unchecked exceptions? You don't have to!

Unchecked exceptions neither have to be caught nor declared. A correct program generally won't raised unchecked exceptions.

๐Ÿค” Why Aren't They Declared?

Having to declare or handle unchecked exceptions would be incredibly onerous:

  • Every object deference can throw NullPointerException, IllegalAccessError, amongst others.
  • Every object construction can throw OutOfMemoryError, NoClassDefFoundError, and more.
  • Every calculation can throw ArithemticException.
  • Every statement can throw VirtualMachineError, InternalError, and still more.

Unchecked exceptions indicate bugs in the program:

  • Array index out of bounds is a bug!
    • Fix the bugโ€”the program is now correct.
  • Division by zero is undefined!
    • Fix the arithmeticโ€”the program is now correct.
  • Running out of memory is a problem!
    • Fix the object allocation loop.
    • Buy more memory.
      • The program is now correct.

๐Ÿ““ Kinds of Unchecked Exceptions

There are two kinds of unchecked exceptions:

  • Runtime exceptions
    • These indicate faults in the program's logic, such as
      • An illegal argument passed to a method
      • A string index out of bounds
      • And so forth
    • A correct program should not need to handle these cases
      • But there are "exceptions" ๐Ÿ˜
        • For example, division by zero when a user types a zero in an input box is a condition you can do something about
  • Errors
    • These indicate faults in the program's environment
      • Out of memory
      • Stack overflow
      • Class circularity
      • Internal error in the Java Virtual Machine
    • A "reasonable" application shouldn't catch these errors!
      • It should be terminated instead.

๐Ÿง Gotcha: the Kinds of Exceptions

Given the above, the logical class hierarchy for Java exceptions is:

โ–ผ Throwable (anything that can be thrown)
    โ–ถ Checked exceptions (which must be handled)
    โ–ผ Unchecked exceptions (which don't have to be handled)
        โ–ถ Runtime exceptions (faults in the program)
        โ–ถ Errors (faults in the environment)

But in the actual class hierarchy in the Java language, it's different:

โ–ผ class Throwable (checked)
    โ–ถ class Error (unchecked)
    โ–ผ class Exception (checked)
        โ–ถ class RuntimeException (unchecked)

In other words, all Throwable objects are checked, except for objects subclassed from Error; and anything derived from Exception is checked, except for objects subclassed from RuntimeException ๐Ÿคฏ

โš ๏ธ Here's the "gotcha":

before();
try {
    something();
} catch (Exception ex) {
    recover();
}
after();

๐Ÿ˜ฎ This is bad, because it catches RuntimeExceptions!

Remember: generally correct programs should not raise runtime exceptions. But because many people catch Exception, they also end up catching RuntimeException:

  • This is like continuing after an assertion fails in C
  • Or writing data at the end of a null pointer
  • Or reading past the end of a file
  • Or using the result of division by zero

Incorrect programs should instead be terminated (with extreme prejudice).

๐Ÿ˜‡ Better Practice

Instead of catching Exception, always try to catch a more specific exception relevant to the program. But when that can't be done, let RuntimeExceptions pass through and then catch the more general Exception. Here's the above example, rewritten:

before();
try {
    something();
} catch (RuntimeException ex) {
    throw ex;
} catch (Exception ex) {
    recover();
}
after();

This works because even though the Exception has a wider scope than RuntimeException, Java processes the catch clauses in listed order. Of course, you should strive to catch more specific exceptions germane to your code.

๐Ÿ—‘ What About Throwaway Code?

If you're just developing little toy examples and the like, you might think there's no need to go through the extra handling of catching RuntimeExceptions to re-throw and then handling the more general Exception.

True! But in that case, don't bother with Exceptionโ€”just use Throwable!

class Throwaway {
    public void somethingElse() {
        try {
            something();
        } catch (Throwable ex) {  // ๐Ÿ† Throwable!
            System.err.println("It foobar'd");
        }
    }
    public static void main(String[] argv) throws Throwable {  // ๐Ÿ† Throwable!
        doSomethingEvenMoreDrastic();
    }

But beware: one developer's trash is another developer's treasure. Some form of that throwaway code might make it into production ๐Ÿ˜