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
Java's compile-time checking does a pretty good job of keeping exceptions safely caged—you can't call a method that throws a checked exception without catching the exception or declaring that your own method throws that exception. (For a great discussion on checked and unchecked exceptions and when to use each, see "Designing with Exceptions" (JavaWorld, 1998).) The compiler will also sometimes stop you from catching an exception that isn't thrown in the try block, but not always, and not when you need it most. This Java Tip discusses this second compile-time check.

Compile-time checking of throws clauses

First, let's distinguish how Java checks the exceptions a method declares it throws from how it checks the exceptions that a catch clause catches. (In this article, when I say exception with a lowercase e, I mean java.lang.Throwable and its subclasses. When I mean a specific class, like java.lang.Exception, I include the package or at least capitalize the class name.) Initially, the approaches seem quite similar: both indicate the exceptions expected to be thrown by the code block with which they're associated. But while Java requires a method to declare the exceptions that it throws, it doesn't require a method to throw every exception it declares for a good reason: Java allows you to design APIs that remain stable as you add functionality.

Consider this initial version of a home-brewed connection pool:

public class ConnectionPool {
   public ConnectionPool() throws ConnectionException {
   }
   public Connection getConnection() throws ConnectionException {
      // Allocate a connection (possibly throwing a ConnectionException or a
      // subclass) if necessary, then return it
   }
}


While the code in getConnection() might throw a ConnectionException, the constructor does nothing, so in this implementation, the method doesn't really need to declare any exceptions. But in the next version, we might rewrite the class to speed up getConnection():

 public class ConnectionPool {
   public ConnectionPool() throws ConnectionException {
      // Allocate all the connections we think we'll ever need
   }
   public Connection getConnection() throws ConnectionException {
      // Allocate a connection if necessary (not likely), then return it
   }
}


Because we made the constructor in the first version declare ConnectionException, code that uses it doesn't have to change to use the second version. Java trades some checking it could do in the throws clause for the sake of long-term stability in all the other classes that call the constructor—not a bad bargain at all.

Compile-time checking of catch clauses

Catch clauses are a different story from throws clauses. The API stability argument doesn't apply: while a method declaration is part of a class's public interface, a try/catch block is an implementation detail hidden from callers. Not only is there no reason for a catch clause to catch an exception that its try block doesn't throw, guaranteeing that it doesn't do so can catch serious coding errors. For these reasons, Java requires your try blocks to actually throw the exceptions that their catch clauses catch. For example, if you were homesick for your old operating system and wrote the following little utility,

  • Print
  • Feedback

Resources