Exceptional practices, Part 2
Use exception chaining to preserve debugging information
In Part 1 of this series, I looked at some general concepts surrounding sensible exception use in Java programs. In this article, I focus in greater depth on a particular exception-handling technique called exception chaining (sometimes called exception wrapping), which illustrates the principles laid out in Part 1. Exception chaining allows you to map one exception type to another, so that a method can throw exceptions defined at the same abstraction level as the method itself, without discarding important debugging information.
Read the whole series on exception handling:
- Part 1: Use exceptions effectively in your programs
- Part 2: Use exception chaining to preserve debugging information
- Part 3: Use message catalogs for easy localization
What’s in an exception?
An exception carries three important pieces of information:
- The type of exception — the exception class
- Where the exception occurred — the stack trace
- Context and explanatory information — the error message, and other state information
Each item is relevant to a different party. Software entities care about the exception class — the JVM and the code that calls the throwing method use it to determine how to handle the exception. The other two items are relevant to people — a developer or support engineer analyzes the stack trace to debug the problem, and a user or developer examines the error message. Each party must receive the information it needs to effectively deal with the error.
A first attempt at wrapping exceptions
To ensure that the first of these characteristics — the exception class — is as useful as possible, it is a common technique for a method to catch one type of exception and immediately rethrow another type. As an example of this technique, consider the ResourceLoader
class below, which a program might use to load a resource such as a graphic or audio file. This implementation of loadResource()
fetches the resource from a database, but nothing in ResourceLoader
‘s specification requires that — the resource could come from a file or a remote server.
public class ResourceLoader {
public loadResource(String resourceName) throws ResourceLoadException {
Resource r;
try {
r = loadResourceFromDB(resourceName);
}
catch (SQLException e) {
throw new ResourceLoadException("SQL Exception loading resource "
+ resourceName: " + e.toString());
}
}
}
loadResource
‘s implementation uses exceptions reasonably well. By throwing ResourceLoadException
instead of SQLException
(or whatever other exceptions the implementation throws), loadResource
hides the implementation from the caller, making it easier to change the implementation without modifying calling code. Additionally, the exception thrown by loadResource()
— ResourceLoadException
— relates directly to the task it performs: loading a resource. The low-level exceptions SQLException
and IOException
don’t directly relate to the task this method performs, and therefore will likely prove less useful to the caller. Further, this wrapping preserves the original exception’s error message so the user knows why the resource could not load (perhaps because of a connection error or an incorrect username or password) and can take corrective action.
Don’t forget the stack trace
However, the loadResource
implementation shown above throws away an important piece of information when it wraps the SQLException
in a ResourceLoadException
— the stack trace where the exception originated. When you create and throw a new ResourceLoadException
object, the stack trace contained in the new exception is the stack trace where the new exception was created, not where the original error actually occurred. If the error is in the loadResourceFromDB()
method, or in some method that it called, you can’t determine the error’s original location because the stack trace is gone. At best, you can guess where it might have been thrown.
If the original exception was thrown from code for which you don’t have source code, such as a vendor-provided class library or a Java Database Connectivity (JDBC) driver, the problem worsens. If you submit a problem report to a vendor, the company will likely ask for information about the exception’s location. But since the stack trace has been discarded, all you can tell them is “somewhere in some code called by your library.”
The solution to this problem is fairly simple — make the original exception part of the new exception’s state information. You can accomplish that quite easily by creating a base class for chained exceptions and simply modifying ResourceLoader
and ResourceLoadException
.
The ChainedException class
You can use the class below, ChainedException
, as a base class for chained exceptions. It provides constructors that take an exception as one of the arguments, and it overrides the printStackTrace()
method to print both stack traces. Concrete exception classes need only extend ChainedException
and provide the desired constructors.
public class ChainedException extends Exception {
private Throwable cause = null;
public ChainedException() {
super();
}
public ChainedException(String message) {
super(message);
}
public ChainedException(String message, Throwable cause) {
super(message);
this.cause = cause;
}
public Throwable getCause() {
return cause;
}
public void printStackTrace() {
super.printStackTrace();
if (cause != null) {
System.err.println("Caused by:");
cause.printStackTrace();
}
}
public void printStackTrace(java.io.PrintStream ps) {
super.printStackTrace(ps);
if (cause != null) {
ps.println("Caused by:");
cause.printStackTrace(ps);
}
}
public void printStackTrace(java.io.PrintWriter pw) {
super.printStackTrace(pw);
if (cause != null) {
pw.println("Caused by:");
cause.printStackTrace(pw);
}
}
}
Integrating ChainedException into our example
Modifying ResourceLoadException
to use ChainedException
is quite simple; just extend ChainedException
instead of Exception
and provide appropriate constructors. If you’ve built a hierarchy of exception classes for your application, as many applications do, just have your base exception class extend ChainedException
. (If you don’t provide a constructor that accepts an exception as an argument, ChainedException
behaves exactly like Exception
, therefore, you can safely use it as a base class for all your application’s exceptions, whether or not they actually wrap other exceptions.)
public class ResourceLoadException extends ChainedException {
public ResourceLoadException(String message) {
super(message);
}
public ResourceLoadException(String message, Throwable cause) {
super(message, cause);
}
}
And modifying loadResource()
method to properly chain the exception is similarly simple; just provide the original exception to the appropriate constructor for ResourceLoadException
:
public class ResourceLoader {
public loadResource(String resourceName) throws ResourceLoadException {
Resource r;
try {
r = loadResourceFromDB(resourceName);
}
catch (SQLException e) {
throw new ResourceLoadException("Unable to load resource "
+ resourceName, e);
}
}
}
The printStackTrace
implementation in ChainedException
as shown above is fairly primitive; you could easily improve it by matching each stack trace’s trailing lines and only printing the lines unique to the outer traces. That would make the stack trace more compact and easier to read.
If we apply the write sensible throws clauses technique outlined in Part 1 — have methods throw exceptions at the same abstraction level as the method itself — it is possible for exceptions to be wrapped several times as they propagate through the program. For example, if our application were a Web server, the IOException
encountered in the course of loading an image might first get wrapped in a ResourceException
and then, as it propagates up the call stack, the exception might get further wrapped in an InvalidReply
exception. But since each outer exception still contains the inner exception, a developer looking at the log file could determine the problem’s exact cause and how it was handled as it passes from layer to layer.
Exception chaining in the JDK
In JDK 1.2, a small number of exception classes incorporated some limited exception-chaining features, such as java.lang.ClassNotFoundException
. However, none of these extended a common chained-exception base class; each class that supported chaining built the chaining features directly into its implementation.
JDK 1.4 has extended the Throwable
class to provide all the features of the ChainedException
base class shown here. If you are developing your application for JDK 1.4 or later, you can simply use the exception-chaining features provided by Throwable
. If you are writing code that must run on JDK 1.2 or 1.3 but will eventually migrate to 1.4, you can use ChainedException
now and then simply change your exception classes from extending ChainedException
to extending Exception
when you migrate.
Get in the habit of using exception chaining
Exception chaining is a useful technique for preserving important error recovery information while at the same time allowing methods to translate lower-level exceptions into higher-level ones as they propagate up the call stack. It will be built into the Throwable
class as of JDK version 1.4. Fortunately for developers not yet developing on 1.4, which at the time of this writing is nearly everyone, you can build exception chaining into your programs now with the simple ChainedException
class provided here, and easily migrate to using the built-in support when you move to JDK 1.4.