Are checked exceptions good or bad?

Find out why some developers love checked exceptions while others hate them

12221514614 6dcba1c70f o
Credit: Oliver Tacke/Flickr

Java supports checked exceptions. This controversial language feature is loved by some and hated by others, to the point where most programming languages avoid checked exceptions and support only their unchecked counterparts.

In this post, I examine the controversy surrounding checked exceptions. I first introduce the exceptions concept and briefly describe Java's language support for exceptions to help beginners better understand the controversy.

What are exceptions?

In an ideal world, computer programs would never encounter any problems: files would exist when they are supposed to exist, network connections would never close unexpectedly, there would never be an attempt to invoke a method via the null reference, integer-division-by-zero attempts wouldn't occur, and so on. However, our world is far from ideal; these and other exceptions to ideal program execution are widespread.

Early attempts to recognize exceptions included returning special values that indicate failure. For example, the C language's fopen() function returns NULL when it cannot open a file. Also, PHP's mysql_query() function returns FALSE when an SQL failure occurs. You have to look elsewhere for the actual failure code. Although easy to implement, there are two problems with this "return special value" approach to recognizing exceptions:

  • Special values don't describe the exception. What does NULL or FALSE really mean? It all depends on the author of the functionality that returns the special value. Furthermore, how do you relate a special value to the program's context when the exception occurred so that you can present a meaningful message to the user?
  • It's too easy to ignore a special value. For example, int c; FILE *fp = fopen("data.txt", "r"); c = fgetc(fp); is problematic because this C code fragment executes fgetc() to read a character from the file even when fopen() returns NULL. In this case, fgetc() won't succeed: we have a bug that may be hard to find.

The first problem is solved by using classes to describe exceptions. A class's name identifies the kind of exception and its fields aggregate appropriate program context for determining (via method calls) what went wrong. The second problem is solved by having the compiler force the programmer to either respond to an exception directly or indicate that the exception is to be handled elsewhere.

Some exceptions are very serious. For example, a program might attempt to allocate some memory when no free memory is available. Boundless recursion that exhausts the stack is another example. Such exceptions are known as errors.

Exceptions and Java

Java uses classes to describe exceptions and errors. These classes are organized into a hierarchy that's rooted in the java.lang.Throwable class. (The reason why Throwable was chosen to name this special class will become apparent shortly.) Directly underneath Throwable are the java.lang.Exception and java.lang.Error classes, which describe exceptions and errors, respectively.

For example, the Java library includes java.net.URISyntaxException, which extends Exception and indicates that a string couldn't be parsed as a Uniform Resource Identifier reference. Note that URISyntaxException follows a naming convention in which an exception class name ends with the word Exception. A similar convention applies to error class names, such as java.lang.OutOfMemoryError.

Exception is subclassed by java.lang.RuntimeException, which is the superclass of those exceptions that can be thrown during the normal operation of the Java Virtual Machine (JVM). For example, java.lang.ArithmeticException describes arithmetic failures such as attempts to divide integers by integer 0. Also, java.lang.NullPointerException describes attempts to access object members via the null reference.

When an exception or error occurs, an object from the appropriate Exception or Error subclass is created and passed to the JVM. The act of passing the object is known as throwing the exception. Java provides the throw statement for this purpose. For example, throw new IOException("unable to read file"); creates a new java.io.IOException object that's initialized to the specified text. This object is subsequently thrown to the JVM.

Java provides the try statement for delimiting code from which an exception may be thrown. This statement consists of keyword try followed by a brace-delimited block. The following code fragment demonstrates try and throw:

try
{
   method();
}

// ...

void method()
{
   throw new NullPointerException("some text");
}

In this code fragment, execution enters the try block and invokes method(), which throws an instance of NullPointerException.

The JVM receives the throwable and searches up the method-call stack for a handler to handle the exception. Exceptions not derived from RuntimeException are often handled; runtime exceptions and errors are rarely handled.

A handler is described by a catch block that follows the try block. The catch block provides a header that lists the types of exceptions that it's prepared to handle. If the throwable's type is included in the list, the throwable is passed to the catch block whose code executes. The code responds to the cause of failure in such a way as to cause the program to proceed, or possibly terminate:

try
{
   method();
}
catch (NullPointerException npe)
{
   System.out.println("attempt to access object member via null reference");
}

// ...

void method()
{
   throw new NullPointerException("some text");
}

In this code fragment, I've appended a catch block to the try block. When the NullPointerException object is thrown from method(), the JVM locates and passes execution to the catch block, which outputs a message.

Exceptions described by Exception and its subclasses except for RuntimeException and its subclasses are known as checked exceptions. For each throw statement, the compiler examines the exception object's type. If the type indicates checked, the compiler checks the source code to ensure that the exception is handled in the method where it's thrown or is declared to be handled further up the method-call stack. All other exceptions are known as unchecked exceptions.

Java lets you declare that a checked exception is handled further up the method-call stack by appending a throws clause (keyword throws followed by a comma-delimited list of checked exception class names) to a method header:

try
{
   method();
}
catch (IOException ioe)
{
   System.out.println("I/O failure");
}

// ...

void method() throws IOException
{
   throw new IOException("some text");
}

Because IOException is a checked exception type, thrown instances of this exception must be handled in the method where they are thrown or be declared to be handled further up the method-call stack by appending a throws clause to each affected method's header. In this case, a throws IOException clause is appended to method()'s header. The thrown IOException object is passed to the JVM, which locates and transfers execution to the catch handler.

Arguing for and against checked exceptions

Checked exceptions have proven to be very controversial. Are they a good language feature or are they bad? In this section, I present the cases for and against checked exceptions.

Checked exceptions are good

James Gosling created the Java language. He included checked exceptions to encourage the creation of more robust software. In a 2003 conversation with Bill Venners, Gosling pointed out how easy it is to generate buggy code in the C language by ignoring the special values that are returned from C's file-oriented functions. For example, a program attempts to read from a file that wasn't successfully opened for reading.

Gosling also pointed out that college programming courses don't adequately discuss error handling (although that may have changed since 2003). When you go through college and you're doing assignments, they just ask you to code up the one true path [of execution where failure isn't a consideration]. I certainly never experienced a college course where error handling was at all discussed. You come out of college and the only stuff you've had to deal with is the one true path.

Focusing only on the one true path, laziness, or another factor has resulted in a lot of buggy code being written. Checked exceptions require the programmer to consider the source code's design and hopefully achieve more robust software.

Checked exceptions are bad

Many programmers hate checked exceptions because they're forced to deal with APIs that overuse them or incorrectly specify checked exceptions instead of unchecked exceptions as part of their contracts. For example, a method that sets a sensor's value is passed an invalid number and throws a checked exception instead of an instance of the unchecked java.lang.IllegalArgumentException class.

Here are a few other reasons for disliking checked exceptions; I've excerpted them from Slashdot's Interviews: Ask James Gosling About Java and Ocean Exploring Robots discussion:

  • Checked exceptions are easy to ignore by rethrowing them as RuntimeException instances, so what's the point of having them?
    I've lost count of the number of times I've written this block of code:
    try {
      // do stuff
    } catch (AnnoyingcheckedException e) {
      throw new RuntimeException(e);
    }

    99% of the time I can't do anything about it. Finally blocks do any necessary cleanup (or at least they should).

  • Checked exceptions can be ignored by swallowing them, so what's the point of having them?
    I've also lost count of the number of times I've seen this:
    try {
      // do stuff
    } catch (AnnoyingCheckedException e) {
      // do nothing
    }

    Why? Because someone had to deal with it and was lazy. Was it wrong? Sure. Does it happen? Absolutely. What if this were an unchecked exception instead? The app would've just died (which is preferable to swallowing an exception).

  • Checked exceptions result in multiple throws clause declarations.
    The problem with checked exceptions is they encourage people to swallow important details (namely, the exception class). If you choose not to swallow that detail, then you have to keep adding throws declarations across your whole app. This means 1) that a new exception type will affect lots of function signatures, and 2) you can miss a specific instance of the exception you actually -want- to catch (say you open a secondary file for a function that writes data to a file. The secondary file is optional, so you can ignore its errors, but because the signature throws IOException, it's easy to overlook this).
  • Checked exceptions are not really exceptions.
    The thing about checked exceptions is that they are not really exceptions by the usual understanding of the concept. Instead, they are API alternative return values.

    The whole idea of exceptions is that an error thrown somewhere way down the call chain can bubble up and be handled by code somewhere further up, without the intervening code having to worry about it. Checked exceptions, on the other hand, require every level of code between the thrower and the catcher to declare they know about all forms of exception that can go through them. This is really little different in practice to if checked exceptions were simply special return values which the caller had to check for.

Additionally, I've encountered the argument about applications having to handle large numbers of checked exceptions that are generated from the multiple libraries that they access. However, this problem can be overcome through a cleverly-designed facade that leverages Java's chained-exception facility and exception rethrowing to greatly reduce the number of exceptions that must be handled while preserving the original exception that was thrown.

Conclusion

Are checked exceptions good or are they bad? In other words, should programmers be forced to handle checked exceptions or given the opportunity to ignore them? I like the idea of enforcing more robust software. However, I also think that Java's exception-handling mechanism needs to evolve to make it more programmer-friendly. Here are a couple of ways to improve this mechanism:

Related:
1 2 Page 1
Shop Tech Products at Amazon