Newsletter sign-up
View all newsletters

Sign up for our Enterprise Java Newsletter

Enterprise Java

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

Understand how Java compilers check catch clauses at compile time

  • Digg
  • Reddit
  • SlashDot
  • Stumble
  • del.icio.us
  • Technorati
  • dzone
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,

  • Digg
  • Reddit
  • SlashDot
  • Stumble
  • del.icio.us
  • Technorati
  • dzone
Comment
Login
Forgot your account info?
Add comment
Anonymous comments subject to approval. Register here for member benefits.
Have a JavaWorld account? Log in here. Register now for a free account.
Resources