Newsletter sign-up
View all newsletters

Enterprise Java Newsletter
Stay up to date on the latest tutorials and Java community news posted on JavaWorld

Sponsored Links

Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs

Java Tip 134: When catching exceptions, don't cast your net too wide

Understand how Java compilers check catch clauses at compile time

  • Print
  • Feedback

Page 2 of 3

 public class rm {
   public static void main(String[] args) {
      for (int i = 0; i < args.length; i++) {
         try {
            new File(args[i]).delete();
         } catch (IOException e) {     // Won't compile!!!
            System.err.println("rm: Couldn't delete " + args[i]);
         }
      }
   }
}


Sun Microsystems' Java compiler would tell you that IOException "is never thrown in body of corresponding try statement." That is because File.delete() doesn't throw any exceptions at all. Instead, it returns false if it can't delete the file. If it weren't for this compile-time exception checking of the catch clause, you might have accidentally written a program that fails silently.

"But wait a minute," you seasoned coders are thinking, "people catch java.lang.Exception in sloppy code all the time, and the code in those try blocks doesn't always throw java.lang.Exception, but it still compiles!" You're right, and here we come to the goal of our expedition: what rule does Java really use when checking the exceptions that a catch clause is allowed to catch?

Catch clauses catch subclasses too

The answer has two parts: The first is that a catch clause catches exceptions of the type of its parameter and its subclasses. This is a valuable language feature as well: a method that declares it throws, for example, javax.naming.NamingException, can really throw any of NamingException's many subclasses. That method's callers needing to know about a specific subclass can write a catch clause for it; those that don't can simply catch NamingException. Furthermore, if the method's implementation changes in a later version, it can throw NamingException subclasses, which the original implementation did not, and its callers need not change. This flexibility contributes greatly to API stability as well. (See "Exceptional Practices, Part 1" (JavaWorld, 2001) for more discussion on how to design an exception hierarchy and throws clauses.)

The fact that catch clauses catch subclasses can also get you into a bit of trouble. For example, many readers might have written something like this utility method:

 public class ConnectionUtil {
   /** Close the connection silently. Keep going even if there's a problem. */
   public static void close(Connection connection) {
      try {
         connection.close();
      } catch (Exception e) {
         // Log the message (using the JDK 1.4 logger) and move on
         Logger.global.log(Level.WARNING, "Couldn't close connection", e);
      }
   }
}


Errors in closing a connection probably don't mean much to the application, so we just catch and log them. But remember that RuntimeException extends Exception. If we catch Exception, as in the example, we'll catch RuntimeException too. A null Connection passed into this method is possibly a much more serious situation—probably indicating a programming error—than failure to close an otherwise valid Connection. If the attempt to call close() throws NullPointerException, you want the error to propagate up the stack to your serious-error handler, not be mistaken for a warning! The solution? Rather than catching Exception, catch the specific exception thrown by the method you're calling. If it throws more than one exception, catch each individually, or catch a common superclass (as long as it's not Exception).

Be careful with those common superclasses too: while catching Exception is the most common case where compile-time checking will let you down, catching any exception with subclasses can put you into a similar situation. You probably want to handle java.io.FileNotFoundException and java.io.EOFException differently, so don't just catch java.io.IOException and lose the distinction. In general, catch an exception superclass only if it's okay for your code to handle each of its subclasses the same way.

Errors and RuntimeExceptions can be caught whether or not they're thrown

By itself, the fact that catch clauses catch subclasses doesn't explain why the following code fragment compiles, even though the only line of code that might throw an exception has been commented out:

try {
   //doSomething(); // Commented out during development
} catch (Exception e) {
   Logger.global.log(Level.SEVERE, "Something failed", e);
}


The second piece of the puzzle is that two exceptions are unchecked, completely exempt from compile-time checking: java.lang.Error and java.lang.RuntimeException. Catch clauses may catch these exceptions whether or not they're actually thrown. Section 11.2 of the Java Language Specification (JLS) explains why these exceptions may be thrown without being caught or declared: briefly, Errors come from the JVM and may occur anywhere, and RuntimeExceptions come from many language constructs (listed in section 15.6 of the JLS) and may occur almost anywhere. Checking for them would not only prove difficult for compiler writers, but would also force programmers to handle situations they can do nothing about.

Now, back to checking catch clauses: the JLS doesn't say specifically that Error and RuntimeException may be caught whether or not they're actually thrown, but that's how Sun's compiler behaves—an empty try block or one containing only statements that throw no exceptions of any kind may have catch clauses that catch Error or RuntimeException.

Put the pieces together

We still haven't explained why Exception, which is checked, can be caught even when it's apparently not thrown. Put the pieces together: RuntimeException extends Exception, so if RuntimeException can be thrown anywhere, Exception can be thrown anywhere too. Similarly, Error extends Throwable, so although Throwable is checked, it can be caught whether or not it is explicitly thrown.

This logical result of Java's exception-checking rules has an awkward consequence. Everyone first reaches for java.lang.Exception when in a hurry. You know the feeling: you're on deadline, you're struggling with some new toolkit, it has a zillion error conditions, and you just don't have time to deal with them separately. Even if all the toolkit's exceptions extend some common subclass, you're not going to look it up right now. To keep moving, you wrap your code in a try block and catch Exception. Oops: you just laid a trap for yourself or the next person who edits your code. Later, when you refactor your prototype, you'll probably split your big try block. Sometimes, that will result in an exception that was thrown in that try block no longer being thrown. In fact, as in the example above, you might even end up with no code that throws any exceptions at all—but since you're catching Exception, you won't find it out from the compiler. That means useless try/catch blocks hanging around, code bloat, and confusion. So don't catch Exception if you can help it—and if, in a pinch, you do, go back later and tighten up those catch clauses.

  • Print
  • Feedback

Resources