Download as doc, pdf, or txt
Download as doc, pdf, or txt
You are on page 1of 4

BEST PRACTICES IN EXCEPTION HANDLING

Exception handling is a built-in aspect of the Java programming language. The concept of handling
error cases with exceptions is designed to make code more reliable and easier to maintain and read.
This tip looks at some best practices for dealing with and handling exceptions.

As a quick refresher, here's the full construct for exception handling:

try {

// code which could potentially


// throw an exception

} catch (TheException1 e) {

// code to handle exceptional condition

} catch (TheException2 e) {

// code to handle next exceptional condition

} finally {

// code to run whether an exceptional


// condition happened or not

Basically, code that might throw an exception goes within a try block. A specific catch block code
runs only if the exception identified for it occurs (or its subclass). However, the code in the finally
block runs under all conditions -- this even includes the case when the try-catch code calls return to
exit out of the current method.

The try block is required. Additionally, one of the two blocks, catch or finally, must be present, or
the compiler will complain.

Given that you can have multiple catch clauses, the system finds the first one that matches the
exceptional condition. So, if you have a catch block for an IOException, and a
FileNotFoundException happens, this is caught by the general IOException catch clause.

This suggests a best practice for exception handling: Always work with as specific an exception as
possible. For instance, if you create a method that could generate a FileNotFoundException, don't
declare that method to throw an IOException. If you do, it forces the caller to handle all
IOExceptions that are possible, not just FileNotFoundExceptions. You shouldn't get too specific
though. In particular, the declared exception thrown by a method should never reveal implementation
details.

Another best practice is try to avoid empty catch blocks. In other words, don't ignore exceptions when
they happen.
// This code is bad

try {
...
} catch (AnException e) {
}

The designers of system libraries throw exceptions for a reason: to tell you some exceptional condition
happened. It's good practice to do something in response, even simply logging the problem. For classes
you design, only declare methods to throw exceptions where you want the caller to deal with a
problem. If you want to ignore an exception, perhaps because of an automatic retry that happens every
minute or two, simply place a comment in the catch block to explicitly say why the exceptional
condition is ignored.

// Better
try {
...
} catch (AnException ignored) {

// System automatically retries every minute


// Nothing to really do until retry

One very important practice related to exceptions thrown from methods is documentation. There is a
javadoc tag for documenting which exceptions are thrown by a method. It's @throws. Best practices
call for all checked exceptions to be documented. Common runtime exceptions should be documented
too. For example, in the following method declaration, both the thrown exception and why the
throwable exception could be thrown are documented. Don't just have the @throws ExceptionName
bit. That doesn't add any more than the method declaration alone.

/**
* Loads the class
*
* @param name
* Class name
*
* @return Resulting <tt>Class</tt> object
*
* @throws ClassNotFoundException
* If class not found
*/
public Class loadClass(String name)
throws ClassNotFoundException {
...
}

For runtime exceptions, an exception here is the parseInt method of Integer. This can throw a
NumberFormatException (this is declared as part of the method declaration). However, because this is
a RuntimeException, it doesn't have to be declared.
Not only don't runtime exceptions have to be declared, but neither do they have to be checked. For
instance, every array access can throw an ArrayIndexOutOfBoundsException. It is not good practice
to wrap all array access code within a try-catch block. Doing that makes code difficult to read and
maintain.

// This is bad, don't do it:


try {
for (int i=0, n=args.length; i<n; i++) {
System.out.println(args[i]);
}
} catch (ArrayIndexOutOfBoundsException e) {
System.err.println("Ooops");
}

For recoverable conditions, it is good practice to have catch clauses with actions involved. These are
typically checked exceptions, although it's also possible with runtime exceptions. Case in point: the
previously mentioned parseInt method of Integer. If you are validating user input with the
parseInt method, that is certainly a recoverable operation (assuming the user is still available).

While it is possible to create your own custom exceptions, more typically, you can reuse the system
exception classes. These are usually sufficient for most needs, though not for every need.

Runtime exceptions that you might use include the following:

 NullPointerException - Tends to be encountered more by accident than by design. If this


commonly happens to you, you might think of peer reviews.

 IllegalArgumentException - Often encountered when validating arguments to a method. In


some cases, they might result from the typical explicit checks at the start of a method. In other
cases, they could happen after various operations are performed. Subclasses include
NumberFormatException and PatternSyntaxException (for regular expressions).
 IndexOutOfBoundsException - The ArrayIndexOutOfBoundsException class is a subclass,
so is StringIndexOutOfBoundsException. You can use these in your classes and methods to
check for passing in an integer beyond the end (or before the beginning) of your data structure.
 UnsupportedOperationException - Typically used by the Collections Framework to indicate
that a requested operation is not supported. You can use it to indicate similar usage problems.

Reusing existing exception classes allows developers to keep their codebase smaller and also take
advantage of the familiarity they have with the existing exception class hierarchy.

The final aspect of exception handling to explore is exception chaining. Introduced with the 1.4 release
of the Java 2 Platform, Standard Edition, exception chaining allows you to attach the underlying cause
of a problem to the thrown exception. Chaining is not usually meant for an end user to see. Instead, it
allows the person debugging a problem to see what caused the underlying problem.

For instance, in the database world, specifically in the JDBC libraries, it is common to have catch
clauses for SQLException. Even prior to exception chaining, the SQLException clause had a
getNextException method which allowed chaining of exceptions. To find out all the underlying
causes of the SQLException, you could have a loop that looks something like the following:

try {

// JDBC code

} catch (SQLException ex) {

while (ex != null) {


System.err.println(
"SQLState: " + ex.getSQLState());
System.err.println(
"Message: " + ex.getMessage());
System.err.println(
"Vendor: " + ex.getErrorCode());
System.err.println("-----");
ex = ex.getNextException();
}
}

Instead of using the JDBC version of chaining, the standard approach is now the one shown below.
Instead of printing the SQL State, message, and error code, just the exception type and message are
displayed.

try {

// Normal code

} catch (AnException ex) {

while (ex != null) {


System.err.println(
"Type: " + ex.getClass().getName());
System.err.println(
"Message: " + ex.getMessage());
System.err.println("-----");
ex = ex.getNextException();
}
}

There is certainly much more that can be done with exceptions. Hopefully, the practices shown here
should help get you started toward better understanding and usage.

For additional information on exception handling, see Chapter 8 "Exceptions" in "Effective Java
Programming Language Guide" by Joshua Bloch.

You might also like