Exception handling: The good, the bad, and the ugly

Even though exception handling is common and every introductory Java course covers it, I still don't think we really know how to do it well. It is almost as if the general concept is still evolving and we are at the midpoint of its trajectory.

Even though exception handling is common and every introductory Java course covers it, I still don't think we really know how to do it well. It is almost as if the general concept is still evolving and we are at the midpoint of its trajectory. This article examines three sides of Java exception handling, the good, the bad, and the ugly, and offer some tips you can use along the way.

Michael C. Daconta is the director of Web and technology services for McDonald Bradley, Inc., where he conducts training seminars and develops advanced systems with Java, JavaScript, and XMLThe good news about exception handling is that it provides a uniform mechanism for error handling. In fact, Java significantly raised the profile of exception handling for application developers by forcing exceptions to be advertised. This enhances and expands the contract of a method to include its error conditions. Let's look at an example that demonstrates this.

The prototype for one of the FileInputStream constructors is:

public FileInputStream(String name) throws FileNotFoundException

Unlike C or C++ prototypes, Java methods and constructors must declare any exceptions that may be thrown when invoking them, using the "throws" keyword. This advertising of exceptions in the method prototype increases the reliability of programming by announcing probable exception conditions to callers so that they can handle them

Here is how we would catch and handle the FileNotFoundException:

try
{
FileInputStream fis = new FileInputStream(args[0]);
// other code here ...
} catch (FileNotFoundException fnfe)
{
System.out.println("File: " + args[0] + " not found. Aborting.");
System.exit(1);
}

Some other good features of Java exception handling are checked exceptions, user-defined exceptions, and the new Java logging API coming out in JDK 1.4.

Every subclass of java.lang.Exception is a checked exception. A checked exception is one that must be advertised from the method that throws it and must either be caught or advertised from the caller.

User-defined exceptions are custom exception classes that extend the java.lang.Exception class. Good Java programs define custom exceptions to encapsulate, report, and handle their unique error conditions. The new Java logging API will allow robust centralized logging of exceptions.

The bad aspects of Java exception handling include abuses such as using unchecked exceptions and catchall constructs. Both of these fall under the category of taking the easy way out.

There is a category of exceptions that are subclasses of RuntimeException and are not checked by the compiler. Examples of this type of exception are NullPointerException and ArrayStoreException. Programmers can subclass RuntimeException in order to circumvent the restrictions of checked exceptions to make it easier for callers of the method. A disciplined programming team should only allow this in rare cases. The second exception handling abuse is catchall constructs. A "catchall construct" is a catch block that handles all possible exceptions thrown to it.

Here is an example of a catchall handler:

try
{
// code here with checked exceptions
} catch (Throwable t)
{
t.printStackTrace();
}

I admit that I have been guilty of using this technique myself for trivial programs; nevertheless, avoid these type of constructs in mission-critical programs unless they are combined with delegation to a central error handler. Except for that case, catchall constructors are just a mechanism to speed programming by avoiding error handling.

Another deficiency in exception handling is the difficulty of devising good strategies for handling errors. Recovering gracefully from low memory situations, write failures, and arithmetic errors is hard to do. Some common techniques to try are looping, saving "rain-day" resources, garbage collecting, and informing the user.

Like many Java features and APIs, there are "gotchas" in exception handling. A common irritation is to allocate, using the "new" keyword, an exception in order to throw it. I cannot count the number of times I have forgotten this only to get spanked by the compiler. In this case, it seems like we are serving the language instead of the other way around. This would be a good place for Sun's language designers to bend the rules (as in the case of String and Array initialization).

My book Java Pitfalls explores several exception handling pitfalls like handling OutOfMemoryErrors, using a finally block for closing files, and parsing exceptions to get the method and line number for an assertion failure. The most important pitfall of these is the need to catch OutOfMemoryError, which is not a checked exception. Running out of memory is fairly common. Any memory-intensive program should catch and handle this error.

Finally, let's consider two features not currently part of Java's exception handling mechanism. First, there is currently no way to resume where you left off after you have successfully handled an exception. Such a feature would be some kind of transparent version of C's setjmp() and longjmp() functions and implemented with a "resume" keyword.

The second missing feature is global exception handlers like C++'s set_new_handler(). A global handler for OutOfMemoryError would be better than making it a checked exception even though it is more common than other subclasses of java.lang.Error. If you feel strongly about these type of features you can suggest new features to the Java platform at the Java Developer Connection.

Even though exception handling is common and every introductory Java course covers it, I still don't think we really know how to do it well. It is almost as if the general concept is still evolving and we are at the midpoint of its trajectory. This article examines three sides of Java exception handling, the good, the bad, and the ugly, and offer some tips you can use along the way.

The good
The good news about exception handling is that it provides a uniform mechanism for error handling. In fact, Java significantly raised the profile of exception handling for application developers by forcing exceptions to be advertised. This enhances and expands the contract of a method to include its error conditions. Let's look at an example that demonstrates this.

The prototype for one of the FileInputStream constructors is:

public FileInputStream(String name) throws FileNotFoundException

Unlike C or C++ prototypes, Java methods and constructors must declare any exceptions that may be thrown when invoking them, using the "throws" keyword. This advertising of exceptions in the method prototype increases the reliability of programming by announcing probable exception conditions to callers so that they can handle them

Here is how we would catch and handle the FileNotFoundException:

try
{
FileInputStream fis = new FileInputStream(args[0]);
// other code here ...
} catch (FileNotFoundException fnfe)
{
System.out.println("File: " + args[0] + " not found. Aborting.");
System.exit(1);
}

Some other good features of Java exception handling are checked exceptions, user-defined exceptions, and the new Java logging API coming out in JDK 1.4.

Every subclass of java.lang.Exception is a checked exception. A checked exception is one that must be advertised from the method that throws it and must either be caught or advertised from the caller.

User-defined exceptions are custom exception classes that extend the java.lang.Exception class. Good Java programs define custom exceptions to encapsulate, report, and handle their unique error conditions. The new Java logging API will allow robust centralized logging of exceptions.

Even though exception handling is common and every introductory Java course covers it, I still don't think we really know how to do it well. It is almost as if the general concept is still evolving and we are at the midpoint of its trajectory. This article examines three sides of Java exception handling, the good, the bad, and the ugly, and offer some tips you can use along the way.

The good
The good news about exception handling is that it provides a uniform mechanism for error handling. In fact, Java significantly raised the profile of exception handling for application developers by forcing exceptions to be advertised. This enhances and expands the contract of a method to include its error conditions. Let's look at an example that demonstrates this.

The prototype for one of the FileInputStream constructors is:

public FileInputStream(String name) throws FileNotFoundException

Unlike C or C++ prototypes, Java methods and constructors must declare any exceptions that may be thrown when invoking them, using the "throws" keyword. This advertising of exceptions in the method prototype increases the reliability of programming by announcing probable exception conditions to callers so that they can handle them

Here is how we would catch and handle the FileNotFoundException:

try
{
FileInputStream fis = new FileInputStream(args[0]);
// other code here ...
} catch (FileNotFoundException fnfe)
{
System.out.println("File: " + args[0] + " not found. Aborting.");
System.exit(1);
}

Some other good features of Java exception handling are checked exceptions, user-defined exceptions, and the new Java logging API coming out in JDK 1.4.

Every subclass of java.lang.Exception is a checked exception. A checked exception is one that must be advertised from the method that throws it and must either be caught or advertised from the caller.

User-defined exceptions are custom exception classes that extend the java.lang.Exception class. Good Java programs define custom exceptions to encapsulate, report, and handle their unique error conditions. The new Java logging API will allow robust centralized logging of exceptions.

The bad aspects of Java exception handling include abuses such as using unchecked exceptions and catchall constructs. Both of these fall under the category of taking the easy way out.

There is a category of exceptions that are subclasses of RuntimeException and are not checked by the compiler. Examples of this type of exception are NullPointerException and ArrayStoreException. Programmers can subclass RuntimeException in order to circumvent the restrictions of checked exceptions to make it easier for callers of the method. A disciplined programming team should only allow this in rare cases. The second exception handling abuse is catchall constructs. A "catchall construct" is a catch block that handles all possible exceptions thrown to it.

Here is an example of a catchall handler:

try
{
// code here with checked exceptions
} catch (Throwable t)
{
t.printStackTrace();
}

I admit that I have been guilty of using this technique myself for trivial programs; nevertheless, avoid these type of constructs in mission-critical programs unless they are combined with delegation to a central error handler. Except for that case, catchall constructors are just a mechanism to speed programming by avoiding error handling.

Another deficiency in exception handling is the difficulty of devising good strategies for handling errors. Recovering gracefully from low memory situations, write failures, and arithmetic errors is hard to do. Some common techniques to try are looping, saving "rain-day" resources, garbage collecting, and informing the user.

Like many Java features and APIs, there are "gotchas" in exception handling. A common irritation is to allocate, using the "new" keyword, an exception in order to throw it. I cannot count the number of times I have forgotten this only to get spanked by the compiler. In this case, it seems like we are serving the language instead of the other way around. This would be a good place for Sun's language designers to bend the rules--as in the case of String and Array initialization.

My book Java Pitfalls explores several exception handling pitfalls like handling OutOfMemoryErrors, using a finally block for closing files, and parsing exceptions to get the method and line number for an assertion failure. The most important pitfall of these is the need to catch OutOfMemoryError, which is not a checked exception. Running out of memory is fairly common. Any memory-intensive program should catch and handle this error.

Finally, let's consider two features not currently part of Java's exception handling mechanism. First, there is currently no way to resume where you left off after you have successfully handled an exception. Such a feature would be some kind of transparent version of C's setjmp() and longjmp() functions and implemented with a "resume" keyword.

The second missing feature is global exception handlers like C++'s set_new_handler(). A global handler for OutOfMemoryError would be better than making it a checked exception even though it is more common than other subclasses of java.lang.Error. If you feel strongly about these type of features you can suggest new features to the Java platform at the Java Developer Connection.

What are your experiences with Java exception handling? Share them with me. Michael C. Daconta is the director of Web and technology services for McDonald Bradley, Inc., where he conducts training seminars and develops advanced systems with Java, JavaScript, and XML

Show Comments