In a recent article called “Bruce Eckel is wrong”, Elliote Harold decided to write a counter-argument to Bruce Eckel’s critique of checked exceptions.
I think that developers trying to argue that checked exceptions are a “failed experiment” and that we should only use runtime exceptions are missing a fundamental point in error handling theory.
How did we get here?
I think the Java exception implementation is fine, but it was initially used by developers who didn’t really understand how it was supposed to work, and because of that, a lot of improper code ended up being committed to the JDK that we now have to live with. Quite a few API’s in the JDK use checked exceptions instead of runtime ones, and for that reason, a lot of programmers ended up developing negative feelings toward the concept, which led to a few absurd extreme claims saying that checked exceptions should never be used, ever.
In reality, the idea of having two different kinds of exceptions makes a lot of sense, as I will show below.
Categorizing the failures
When an error occurs in a program, there are two things I want to know about it: 1) is it expected? and 2) is it recoverable?
Let’s take a closer look:
Let’s discuss each of these in turn:
- X I crossed this option because I just can’t come up with a scenario in which we can recover from an unexpected exception.
- (a) Recoverable and Expected. This is a pretty common scenario that is covered by Java’s checked exceptions. A typical example is FileNotFoundException: very often, you either want to let the user know that you couldn’t find the file, or if this is not information the user cares about, you can always log it or ignore it.
- (b) Unexpected and not recoverable. This scenario is covered by Java’s runtime exceptions. A typical example is NullPointerException. Of course, null pointers are pretty common in Java code, but you know this situation is not expected because if it were, the developer would have guarded against it ("if (p != null)"). Because it’s unexpected, your system is now in an unstable state and it’s likely that your application will either crash or behave erratically, so this situation is not recoverable.
- (c) Expected but not recoverable. Interestingly, I have found very little documentation of this scenario. After all, if the exception is expected, shouldn’t we be able to recover from it? Well, not always. Read on.
Understanding scenario (c) requires realizing that the same exception can sometimes be recovered from and sometimes not, depending on when it happens.
For example, you could imagine a situation where an application needs to open the same file in two different places. The first time it does, not finding this file means asking the user to provide a different file. But once this file has been supplied, the system can rightfully that it’s there, so the second time it needs to open this file, it will expect to find it. But what if something happened that made this file disappear in-between these two times? (e.g. hard drive failure). Suddenly, our exception has become unrecoverable.
This seems to lead us down a new path: it appears that we need two different exceptions for FileNotFound: one that is checked, and one that is not.
Toward a solution
If I had a chance to redo Java’s exception mechanisms, there is actually very little I would change. First of all, I believe that we need to keep the checked/unchecked distinction, which maps very well to the real world, as I just demonstrated.
However, I would probably pick different names to make the difference between these two concepts more palatable. The JDK uses the class Error for this but the difference between an Error and an Exception is quite subtle and often misundertood. To make matter worse, the JDK uses the word “Exception” to call both checked and runtime exceptions. No wonder people are confused.
How about Panic, or maybe Bug instead of Error? I know this sounds a bit childish, but it has the merit of being very clear and when someone not familiar with these concepts is writing new Java code, deciding whether they should throw an Exception or a Bug should be easier.
Second, I would provide two versions of the most common exception classes: one that is an Exception and one that is a Bug. For example, we would have both a FileNotFoundException and a FileNotFoundBug:
- You are asking the user to enter a number and they type in a letter? Your application should throw a NumberFormatException and catch it so the user can fix their error.
- You are trying to read a preference file that you saved earlier and a number cannot be parsed correctly? It’s likely that the file got corrupted and you probably want to throw a NumberFormatBug
Having said that, I’m not a fan of the idea that the entire exception hierarchy is now going to be duplicated, so maybe we could push this idea a bit further and use Generics to cut down on the number of classes:
throw new FileNotFound<Bug>();
throw new FileNotFound<Exception>();
Of course, the compiler would have to apply different rules depending on the Generic parameter, which I don’t believe is feasible today, but you get the idea.
Moving things forward
It’s a pity that the debate on exceptions has become so polarized, because I really think that if Java’s dual approach to exception handling is not the best way to support failures in programs, it’s certainly a step in the right direction.
In the future, I’d like to see discussions on this topic focused more on where and when to use checked exceptions and runtime exceptions instead of reading articles telling you that you should always do “this” and never do “that”.