Thursday, 23 December 2010

So What's Wrong with Checked Exceptions?

In part one of this blog post on Checked Exceptions, I defended them. They have a very valuable place in Java's toolbox, and those who argue for their removal are missing the point of them.

The trouble is, they're overused. In the early days of Java, everyone assumed that Checked Exceptions were the "right" things to use in all circumstances.

But when you throw a checked exception, you are forcing the caller to handle the problem. But often, problems can't be possibly be handled by program code. The number one example of this is when writing JDBC (data access) code:

try
{
   String sql = "INSERT BOOK (bookID, ISBN, title, author, price) VALUES (?,?,?,?,?)";

   PreparedStatement stmt = con.prepareStatement(sql);

   // bind the parameters - omitted
   stmt.executeUpdate();
}
catch (SQLException e)
{
   ???
}

(This is just a fragment for illustration - don't use this in any real code. I've omitted the closing of the connections - don't even get me started on that nightmare).

Now, seriously, what code can you possibly put in this catch block? Firstly, there are hundreds of things that could go wrong here:

1) Network connection to database down (a squirrel has eaten a cable somewhere)
2) No access to database allowed (bad credentials)
3) SQL statement grammar incorrect - [in fact - it IS! I deliberately made a typo in the SQL]
4) Datatypes of data wrong
5) Our DBA has gone mad/got drunk and dropped the table for a joke
6) Constraint violation

I could go on. But the point is:

None of these problems can be handled in the catch block. How can your code possibly fix the SQL error in my hardcoded string?

So what CAN we do? Well, if we continue to use checked exceptions only, then we have to throw the exception to the caller. But think about it - how can our caller (whatever that is) deal with any of the six problems above? Almost certainly, they can't so they will re-throw it too. And their caller will have to rethrow it, and so on until it reaches probably the top level of our call stack.

The worst aspect of this is that many Java programmers don't get this - they think because they've got an exception, they MUST deal with it there and then (they forget it can be rethrown). Which is why you'll often see this disgusting code in production:

try
{
   // SQL and JDBC as above
}
catch (SQLException e)
{
   e.printStackTrace();
}

Which translates as "hide the problem in a log file somewhere and then let's carry on as if all was well".

Probably the best you can do is to manually convert it into an unchcked exception. Since JDK1.4, you can wrap exceptions so that you don't lose the detail of the original cause:

try
{
   // SQL and JDBC as above
}
catch (SQLException e)
{
   throw new RuntimeException(e);
   // or you you create your own runtime exception like DataAccessException
}

SQLException should have been made an *Unchecked* Exception in the first place. This would have meant that we could let the problem rise up the stack until it reaches an appropriate level where we should handle it.

If you're in the lucky position of writing web applications, then you need never catch the unchecked exception - because web applications run in a container like Tomcat, effectively the Tomcat code is the "top level" of your app - you simply configure Tomcat to catch all unhandled exceptions, and to report them to the user and the system admin.

This is why the Spring Framework supplies a wrapper layer around JDBC, which automatically catches the checked exceptions for you, and it throws them to your code as unchecked exceptions. We talk about this in detail on the Spring Framework Online Course

When to use Checked or Unchecked?

Checked and Unchecked Exceptions are two very different tools. In fact, I suspect that the confusion about them arises because they've been given very similar names (even worse, the words Checked and Unchecked don't even appear in the API). The designers of Java should have given them clear distinction in the API.[1]

  • Checked exceptions are for situations where you know your method has reached a condition which means the end goal of the method can't be achieved, but somewhere in the call chain we except the problem can be dealt with.
  • Unchecked exceptions are for situations where something has gone wrong, but due to the nature of the problem, it's likely that the problem can't be handled (or would be best caught with a global error handler). Perhaps a configuration problem (eg a vital config file is missing) or an environmental problem (eg a database table has disappeared), or maybe even a problem within the code (eg a hardcoded SQL statement is wrong).
The point is, they are different tools and both have their strengths and weaknesses. Java is unique in having Checked Exceptions, and it would be a shame to see them removed from a future version of Java (as some people want).





[1] The inheritance hierarchy is broken also. RuntimeException extends Exception, which technically means that RuntimeException is a special kind of Exception. When it really, really isn't (perhaps you could argue that Exception is a special kind of RuntimeException). This means that whenever you catch raw "Exception", you are also catching RuntimeExceptions, even those such as NullPointerException, which is definitely something you don't want to do.

No comments:

Post a Comment