How to write the best exception constructors
This article describes how to write the best exception constructors. It is related to Item 63 (Include failure-capture information in detail messages) of Joshua Bloch's tremendous book - "Effective Java: Second Edition". If you haven't got it yet, what are you waiting for?
Let's start by discussing the importance of the string returned from an exception's toString method. When an exception is logged, it is usually accompanied either by its stack trace or by the string returned from its toString method. However, according to the Javadoc of the Throwable.printStackTrace method, the first line of a stack trace "contains the result of the toString() method". Therefore, the most useful debugging aid an exception can offer is a toString method that returns details of anything that contributed to the exception. This article is going to show how to write exception constructors that make it easy to have a great toString method.
There are three main types of exceptions:
- those for general use
- those for a specific condition
- those used as the abstract base class of an exception hierarchy
The guidelines for constructor creation depends on the type.
General-use exceptions
General-use exceptions are intended for use in a variety of situations. Examples of such exceptions are java.lang.IllegalStateException and java.text.ParseException. When an exception has such a high potential for reuse, it's best to mimic the four constructors offered by java.lang.Exception. In particular, for FooException, define the following constructors:
- FooException()
- FooException(String message)
- FooException(Throwable cause)
- FooException(String message, Throwable cause)
Specific exceptions
Exceptions such as java.io.FileNotFoundException are only intended for use in a specific situation. The best approach when writing specific exceptions is to provide constructors that force a useful toString method. For example, write FileNotFoundException as follows:
/** * A checked exception that indicates that a file is not found. */ public final class FileNotFoundException extends IOException { /** * Constructs a new instance of FileNotFoundException. * * @param file the file that was not found */ public FileNotFoundException(final File file) { super(getMsg(file)); } private static String getMsg(final File file) { if (file == null) { return "null"; } else { return file.getAbsolutePath(); } } }
When the constructor is defined to accept a File, the class is easy to use and hard to misuse. All instances return a useful string from their toString method. For example, if file is not null, the return value of toString is com.kblaney.FileNotFoundException: D:\temp\kyle.txt
How's that for a string that includes everything I need to know to debug a problem? The exception's class name tells me what went wrong (a file was not found) and the exception's message provides the absolute path of the file that was not found.
Note that FileNotFoundException has already been defined in Java and its existing constructors can't be removed for backwards-compatibility reasons. However, in this case Java is not something to be emulated. We can do better!
Abstract base class of exception hierarchy
An abstract base class of an exception hierarchy should provide constructors that help its derived classes follow the above rules. If there are many derived classes, it is probably best to mimic the four constructors offered by java.lang.Exception. If there are few derived classes, the abstract base class should mimic the constructors required for any of the derived classes.