I am a software engineer at Google on the Android project and the creator of the Java testing framework TestNG. When I'm not updating this weblog with various software-related posts or speaking at conferences, I am busy snowboarding, playing squash, tennis, golf or volleyball or scuba diving. Cedric is a DZone MVB and is not an employee of DZone and has posted 90 posts at DZone. You can read more from them at their website. View Full User Profile

Improving Exceptions

04.25.2010
| 7255 views |
  • submit to reddit

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:

  Recoverable Not recoverable
Expected (a) (c)
Unexpected X (b)

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>();

or 

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”.

From http://beust.com/weblog/2010/04/25/improving-exceptions

Published at DZone with permission of Cedric Beust, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Tags:

Comments

Steve Banks replied on Mon, 2010/04/26 - 2:26am

I really think all exceptions should be checked exceptions. If a lazy developer doesn't want to deal with exceptions thrown by Method X, then it's really quite trivial to write a wrapper around this class which gobbles up any exceptions.

 That being said,  multiple-catch would've been really nice. But I guess that's coming soon, at least :-)

Chris Ainsley replied on Mon, 2010/04/26 - 3:57am

I am also a supporter of the current Java exceptions mechanism. It allows for expected Exceptions to be chained and decorated in a very elegant way and allows non-expected exceptions to be either pro-actively dealt with or escalated to an level that can safely recover or concisely but descritively report unexpected exceptions.

Unchecked exceptions also have their use. I don't really want to have code where every method has to declare NullPointerException or InvalidArgumentException. We are aware these can happen at any time anyway, especially when referencing unknown APIs. The important thing is to localize recovery points and to pass up well-formatted diagnostic information through the exception tree.

Checked exceptions are important as they allow a framework to label the expected types of exceptions from a method. I agree with the article that exceptions should not be used for control of flow, but checked exceptions do allow for control of error recovery, being able to deal with particular failure scenarios in different ways.  Checked exceptions do force the programmer to consider "what happens if" rather than it cruising up the stack where critical information about the context of the failure is no longer available and the exception may not be recoverable any more.

Itis better to avoid Exceptions wherever possible. Null pointer exceptions of course should never ever be thrown. It is without exception (pun intended) always a bug. But sometimes exceptions cannot be avoided, especially when dealing with external factors. A method that has a good likelyhood of not being able to return a value through external factors beyond the programmers control SHOULD declare a checked exception, to warn the programmer that they should prepare a contingency. If the exception can't be dealt with, the programmer can always decorate it and pass it up the chain.

I don't quite understand criticism of checked exceptions. In my experience, it is a good system, that works well and with the addition of multi-catch, it only has as much boiler-plate as is needed.

Ricky Clarkson replied on Mon, 2010/04/26 - 4:19am

I prefer returning an object that either contains a result or represents an error, e.g., Either[Fail, Integer] parseInt(String s).

But given that one chooses exceptions over that, a 'rethrows' syntax would help a lot with those checked exceptions that the caller doesn't care about:


void createFooURL(String host) rethrows MalformedURLException {
    return new URL(host + "/foo.cgi");
}

There are more flexible alternatives to that syntax, but for now the above should get the point across.

Osvaldo Doederlein replied on Mon, 2010/04/26 - 9:09am

Thumbs up again for the checked exceptions. People who claim that this is a misfeature of Java have probably never written a large-scale application with critical reliability requirements - where you really need thousands of catch blocks, and you really need to fill virtually all these blocks with some significant code (from simple but careful logging, to proper wrap-and-rethow, to a pages-long retry or recovery mechanism plus some monitoring). In other words, get off my lawn :-)

The excusion of checked exceptions is half of Microsoft's major bunders when they decided to "fix" Java, creating C#. The second major blunder is their horrendous naming conventions with identical ClassName and MethodName() styles - it just burns my eyes, whenever I do some quick C# coding. :-( And it's sad to realize that both changes were probably justified mostly by the need to not copy Java too obviously, back in .NET 1.0.

The only real alternative I see to checked exceptions is something even more sophisticated (but also checked/mandatory) like some modern design-by-contract facility, or richer typesystems that can embed many correctness constraints (e.g. in Haskell - but these tend to be considered too complex, and I cannot judge from my superficial knowledge).

Casper Bang replied on Mon, 2010/04/26 - 12:41pm

Checked exceptions pollute the signature, distract attention to secondary things and creates complexity and high coupling in a call-hierarchy. They solve nothing that we can't do with modern IDE's or the concept of code-contracts (which goes far deeper than handling alternative exit values).

The JDK authors were not wise enough to make IOException a RuntimeException so it's silly to expect novice programmers to do better! If something is that hard to get right, it probably should be left out of the core language. Thankfully most modern frameworks that I know of, adopted an opt-out attitude.

Osvaldo Doederlein replied on Mon, 2010/04/26 - 1:10pm in response to: Casper Bang

"Checked exceptions pollute the signature" - advocates of dynamic-typed language can say the same about parameter and return type declarations. Ditto for most other points. Yes, checked exceptions does have some costs; the debate is whether these costs are higher or lower than the benefits. I am definitely in the side of static declarations, for exceptions just as for types, for similar reasons.

Complex code bases that go without exception handling are often very brittle in their approach to error handling. You can document all possible exceptions in your API's javadocs, but how many people will actually read and handle that stuff properly? There are some apps that can get away with simple error handling models like "catch anything in some root method" (remarkably GUIs), but this clearly doesn't apply to many other apps.

The blunders in JDK APIs are mostly found in early APIs, like java.io; and we can dig a ton of other bad API designs in JDK 1.0 and even 1.1 (or even 1.2 like Swin... ok, let's not digress.) So the problem is clearly not related to exceptions. There are also some mistakes that were caused by the application of principles like James Gosling's famous "8 fallacies" doctrine from Peter Deutsch - I think JG believes to this date that checked RemoteException was the right thing to do. (I've swallowed that myself for years; it's hard to not bow to the voice of authority. Thanks god EJB 3.0 sanitized this for good.)

Robert Elliot replied on Mon, 2010/04/26 - 2:39pm

I like the concept of checked exceptions, but think it is quite mistaken to imagine that the writer of a method can know whether clients will view the errors it can raise as recoverable or not. It will depend entirely on the context in which the method is called. What is needed is a mechanism such that the writer of the method can make the exceptions checked, so that the compiler reminds the writer of client code what scenarios they might want to consider, but simultaneously make it trivial for a developer to translate those checked exceptions into runtime exceptions if they consider they cannot meaningfully handle them or believe they should never occur (i.e. they are a bug).

I blogged a possible solution here: http://softwaredevthoughts.blogspot.com/2010/01/checked-and-unchecked-exceptions.html

Alex(JAlexoid) ... replied on Mon, 2010/04/26 - 3:31pm in response to: Osvaldo Doederlein

@Osvaldo Doederlein
   ...proper wrap-and-rethow...

 To paraphrase Steve Jobs: If you are doing wrap-and-rethrow - you blew it.

@Osvaldo Doederlein   
"Checked exceptions pollute the signature" - advocates of dynamic-typed language can say the same about parameter and return type declarations. Ditto for most other points. Yes, checked exceptions does have some costs; the debate is whether these costs are higher or lower than the benefits. I am definitely in the side of static declarations, for exceptions just as for types, for similar reasons.
...
Complex code bases that go without exception handling are often very brittle in their approach to error handling.

 

I am not an advocate of dynamically typed languages, far from it, but...

Checked exception pollute the code base so much, that in most cases that they become useless where they matter. This leads to hierarchy of exceptions, exception frameworks, exception helpers and so on... There should be a much more sensible approach. That is, all exceptions should be unchecked by default, only particular exceptions should be eligible to be checked.

And I can't complain enough about large and brittle code bases that have 99.9% of their methods throwing some kind of exception. Default checked exceptions just make people feel better, while rarely bringing anything useful. The most stable code base I've seen had surprisingly little visual clutter(a la throws Excception1, Exception2...) resulting in less defects.

My policy now is exceptions should be caught and processed where they need to be reported or at the boundaries(JVM, code base, back-end to UI, etc.). As little as possible checked exceptions. And coverage will still weed out most problems.

Osvaldo Doederlein replied on Mon, 2010/04/26 - 4:19pm in response to: Alex(JAlexoid) Panzin

"To paraphrase Steve Jobs: If you are doing wrap-and-rethrow - you blew it." - I am really not a fan of Jobs (at least in his current evil incarnation), so that doesn't hurt me. ;-)

And, you are wrong; there are situations where catching an exception, wrapping it in another exception and rethrowing that (possibly combined with some extra handling - like some cleanup) is a good approach. Mind the qualifier "proper". I'm not advocating frequent, mindless use of wrap-and-rethrow. Just for one quick example, you maye have a DAO pattern that translates persistence API-specific exceptions (like SQLExceptino or FinderException) into some neutral exception, so all code that calls these DAOs needs no change if we eventually have to change the persistence mechanism. (I've had two major projects that needed this transition - one from JDBC to JPA, another from EJB-CMP to JDBC & JPA.) Wrap-and-rethrow is (among other uses) the exception handling implementation of the Adapter pattern.

"Checked exception pollute the code base so much, that in most cases that they become useless where they matter" - once again you are claiming that checked exceptions are bad because people won't use them properly. You can have an applicatin that has a good exception-handling design, using checked ones only when it's a good idea, so there's no big pollution etc. (like Cedric points, those that are Expected; all the Recoverable ones and some of the Unrecoverable ones - the latter are the only difficult case for the designer.)

"Coverage will still weed out most problems" - tests and code coverage are great mostly for the Unexpected exceptions. For expected ones (e.g. those produced by bad input data), testes with good code coverage are still important but it's rather to make sure you have tested both the Happy Day scenarios but also the exception flows - you've got to make sure that those important exceptions are caught and handled properly. For checked exceptions, you only need tests to assert that some nontrivial handling code is correct - the compiler makes sure that you have, at lthe very least, handled the exception somewhere... and we are all over again in the dynamic X static-typed debate, where this same argument of "don't need static declarations because I will write a million tests" is often used, which makes me sick, just check this for example.

Cloves Almeida replied on Mon, 2010/04/26 - 4:20pm

Checked exception are bad because they encourage wrap and rethrow. Think of an embedded file-based DBMS, used via JPA.

Persistence.createEntityManagerFactory() will wrap the FileNotFoundException in a RuntimeException. To recover and ask the user for a different file, the developer would have to unwrap the FNFE and do some magic to distinguish it from any other Exception.

The right solution would be to simply throw the unchecked FNFE and let it escalate until some layer happens to deal with it.

And IMHO, exceptions are supposed to be unexpected, else they'd be part of the flow. What's so wrong about checking "if (file.exists())"?

 

cowwoc replied on Mon, 2010/04/26 - 6:23pm

Anyone who thinks that checked exceptions are bad should first read the Effective Java chapters that discuss it and then get back to us. If you *still* think they are a bad idea then by all means let's please discuss them further.

 

Most of the time that people complain about checked exceptions have to do with SQLException and IOException. There are two separate problems at play:

1. Some methods throw coarse-grained exception such as IOException instead of FileNotFoundException making it difficult to reasonably recover from them. Methods should only throw finest-grained exception types (FileNotFoundException) and users should make sure to catch equally fine-grained typesas opposed to "catch (Exception)"

2. Most of the time SQLException is thrown it isn't reasonable to expect that the caller can recover from them, but in some cases it is. For example, no application can recover from typos in SQL queries but some applications may wish to recover from "table does not exist" errors by creating it. When in doubt, go with unchecked exceptions

Osvaldo Doederlein replied on Tue, 2010/04/27 - 8:17am in response to: Cloves Almeida

"Checked exception are bad because they encourage wrap and rethrow." - This is as fallacious as, for example, "immutable String is bad because it encourages excessive allocations/copies to compose complex string, or memory leaks through retention of small strings created as shared substrings of much bigger strings". That's a stupid argument, because any half-decent programmer will know better. If you are going to ban each language feature that can be misused by bad/inexperienced programmers just because some particular idiom (that is often but not always wrong) is easy to code, then I'm afraid your language won't be left with enough features to build any program. And this is true for every language that I know.

"What's so wrong about checking "if (file.exists())" - If you're reverting to old-style validation through return codes like in the old days of C, why do you need exceptions at all? You are trying to kill exceptions at least for the Expected case. And this is exactly the case where structured exception handling (either checked or not) is most useful, because you typically have some custim handling for these errors (instead of leaking them all way down the call stack for a catch-all handler). Then you need the choice of either handling the error immediately/locally, or allowing some caller to handle it(*). In languages without exceptions, this is very frequently necessary too, but then it's a nightmare (have to write convoluted, ad-hoc error propagation; work around functions that already return some valid value e.g. with extra error out-parameters; worry about consistent error code representation across modules; add extra boilerplate code to check-and-propagate error in every function that doesn't want to handle an error...)

(*) If you answer "but I think it's always better to handle expected errors locally", well in that case I guess you are one of these programmers who write 500-line methods. The need to handle expected exceptions in another method is extremely frequent in modern-style, well decomposed code. For example you may have a method myTransaction() that invokes myQuery1() and myQuery2(), but both queries invoke e.g. executeQuery() which throws a single exception, like SQLException. Instead of duplicating the exact same error handling in both myQuery1() and myQuery2(), you may just leak them to myTransaction() where a single catch will handle all SQLException's. Perhaps myTransaction() was initially a big, monolithic method, then you refactored each query into a private method. Now you can easily see the value of leaking exceptions to the caller - without this facility, you are in refactoring hell.

Michael Parmeley replied on Tue, 2010/04/27 - 9:21am

I have no problem with checked exceptions, what gets on my nerves are the exceptions in the JDK that are checked that should be unchecked.

 The MalformedURLException drives me nuts; this can't be recovered from why is it checked? The same thing for UnsupportedEncodingException...ARRGHH!!

 The NumberFormatException is probably correctly an unchecked exception; however, what drives me crazy about this one is that you have no choice but to use exception handling for flow-control with this one. Java should provide a method like isParseable() or something like that on all the wrapper types so you can check it without depending on throwing an Exception (I recently took a look at Groovy and I see that it does provide methods like that).

 

@Casper IMHO IOException is correctly a checked exception. If I am reading/writing a Socket I can recover from an IOException by reconnecting. I think the problem might be that the IOException is too all encompassing. There should be finer grained exceptions for various situations that can give rise to IOException.

 

Cloves Almeida replied on Mon, 2010/05/03 - 6:21pm

Okay, I'll rephrase. Checked exceptions, the way they're used and abused even by the JDK, encourage bad programming. Back in the 90's exceptions were a not so well understood concept. And forcing a badly understood "feature" into the developers gave us gems like checked IOException, SQLException and MalformedURLException.

If Sun itself followed the checked->expected, unchecked->unexpected (they could've named it that way!) world would be marvelous. Even though I still think expected exceptions are better expressed as control flow.

 

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.