Bruce Eckel is Wrong

Every time the subject of checked versus runtime exceptions comes up, someone cites Bruce Eckel as an argument by authority. This is unfortunate, because, as much as I like and respect Bruce, he is out to sea on this one. Nor is it merely a matter of opinion. In this case, Bruce is factually incorrect. He believes things about checked exceptions that just aren’t true; and I think it’s time to lay his misconceptions to rest once and for all.

Let’s see exactly what Bruce’s mistake is. The following is an extended selection from Thinking in Java, 4th edition, pp. 490-491:

An exception-handling system is a trapdoor that allows your program to abandon execution of the normal sequence of statements. The trapdoors used when an “exceptional condition” occurs, such that normal execution is no longer possible or desirable. Exceptions represent conditions that the current method is unable to handle. The reason exception-handling systems were developed is because the approach of dealing with each possible error condition produced by each function call was too onerous, and programmers simply weren’t doing it. As a result, they were ignoring the errors. It’s worth observing that the issue of programmer convenience in handling errors was a prime motivation for exceptions in the first place.

One of the important guidelines in exception handling is “Don’t catch an exception unless you know what to do with it.” In fact, one of the important goals of exception handling is to move the error-handling code away from the point where the errors occur. This allows you to focus on what you want to accomplish in one section of your code, and how you’re going to deal with problems in a distinct separate section of your code. As a result, your mainline code is not cluttered with error-handling logic, and it’s much easier to understand and maintain. Exception handling also tends to reduce the amount of error-handling code, by allowing one handler to deal with many error sites.

Checked exceptions complicate the scenario a bit, because they force you to add catch clauses in places where you may not be ready to handle an error. This results in the “harmful if swallowed” problem:

try {
// ... to do something useful
} catch (ObligatoryException e) {} // Gulp!

Do you see the mistake? It’s a common one. Let me repeat it, so it’s really obvious [emphasis mine]:

Checked exceptions complicate the scenario a bit, because they force you to add catch clauses in places where you may not be ready to handle an error.

This is false. You are never forced to add catch clauses where you are not ready to handle an error. This isn’t a matter of opinion. It’s a matter of fact. Checked exceptions do not require catch blocks. They require a catch block OR a throws declaration. Eckel’s entire argument is based on ignoring the possibility of a throws declaration.

While Bruce is absolutely right that you should not catch an exception unless you know what to do with it, this in no way means that you should insert a catch block everywhere a checked exception may be thrown. If you aren’t ready to handle an error at one place, let the exception bubble up. If a checked exception is thrown inside a method where you are not ready to handle it, then the correct response is to add a throws clause to the method indicating that the exception will bubble up from that method. For example,

 public void doSomethingUseful() throws ObligatoryException {
   // ... do something useful that throws an obligatory exception
} 

You do not and should not insert a catch block in a method where you cannot do anything reasonable in the catch block. Checked exceptions never meant that every exception had to be caught as soon as it was thrown. It is perfectly acceptable to declare that a method throws a checked exception. Indeed, this is exactly how exceptions are meant to be used. It warns whoever calls your method that they need to be ready for this exceptional condition, and they either need to catch it and handle it themselves; or, they themselves need to declare that they throw it so that they warn their callers.

Yes, if it were true that every checked exception needed to be caught immediately, then checked exceptions would be incredibly inconvenient. However, experienced Java programmers don’t do this. Catching each and every checked exception at the first opportunity is a sure mark of a novice Java developer.

Occasionally, you’ll override a method inherited from a superclass or implement a method declared in interface that does not declare it throws the checked exception that your method throws and thus can’t throw the correct exception. In this case alone, it may be acceptable to wrap the exception in either a checked exception that the original declaration declares or in a runtime exception (if the original declaration does not declare any appropriate checked exceptions). For example,

   @Override
    public void doSomethingUseful() {
        try {
     // ... do something useful that throws an obligatory exception
        } 
        catch (ObligatoryException e) {
            throw new ObligatoryRuntimeException (e);
        }

However, you still don’t need to handle the exception before you’re ready for it.

I will note that this situation is a failure of design. When you’re forced to do this, one of two things is broken:

  1. The superclass/interface was not designed properly for extension. Specifically it did not take into account the exceptions overriders/implementers might reasonably want to throw. The method likely should have been declared final and probably shouldn’t be extended at all.
  2. The overriding/implementing method is violating the contract of the method it overrides/implements by doing something it really should not be doing.

A good example of the latter would be letting an IOException wrapped in a RuntimeException escape from the run() method of java.lang.Thread or java.lang.Runnable. These methods do not have sufficient context to handle such an exception, but something further down in the call chain does. The exception should be handled before it bubbles all the way up (though not necessarily in the same method where it’s first thrown).

In a method properly designed for extension, an empty throws clause (or a throws clause that does not match the actual exception) indicates that callers of that method cannot handle and do not expect such an exception. An overriding method that throws a new exception is violating the contract by failing to handle it, the same as it would were it to restrict a precondition or loosen a postcondition. (In essence, this is another form of loosening a postcondition.)

Eckel is not the only one to make the mistake of assuming that checked exceptions must be immediately at handled at the first possible opportunity. For example, he cites Barbara Liskov and Alan Snyder explaining their decision not to include checked exceptions in CLU:

requiring that the text of a handler be attached to the invocation that raises the exception would lead to unreadable programs in which expressions were broken up with handlers

True. Requiring that the text of a handler be attached to the invocation that raises the exception would lead to unreadable programs. Fortunately neither Java nor checked exceptions require any such thing. When handlers are appropriate they can usually be moved to the end of a method, far away from the the invocation that raises the exception. When handlers are inappropriate, you use a throws clause instead. Immediately following every statement that can throw an exception with a catch block is bad form on a par with goto.

Liskov and Snyder continue:

“We felt it was unrealistic to require the programmer to provide handlers in situations where no meaningful action can be taken.”

Also very true, but of course, checked exceptions do not require the programmer to provide handlers in situations where no meaningful action can be taken. When no meaningful action can be taken (out of memory error, stack overflow, class not found, etc.) Java programs throw Errors, not checked exceptions, not even runtime exceptions. Checked exceptions signal environmental problems that programmers cannot prevent or predict, should test for, and most decidedly can handle. To choose the most extreme example, if a production-worthy database system is writing a file and the disk fills up, it should handle the condition gracefully without corrupting the database. A disk full error is neither unforeseeable nor unmanageable. Most checked exceptions aren’t even that tricky to respond to.

What checked exceptions actually do require is that any method that can throw a checked exception warn its callers that the exception may be thrown. That’s all. A checked exception is nothing more and nothing less than part of the return type. Methods may return normally or they may throw exceptions. It makes just as much sense to specify the exceptions that can be thrown by a method as it does to specify that a method returns an int or a String. A method that does not declare the exceptions it throws is incomplete.

A lot of us didn’t really get checked exceptions when Java was released in the mid-nineties. It was a genuinely new idea that I don’t think any programming language before had foreshadowed. Liskov and Snyder wrote the paper quoted here in 1979, and their quotes make sense if you assume they simply didn’t conceive of having different kinds of exceptions in the language. if you only have one kind of exception then it makes sense for it to be a runtime exception rather than a checked exception.

Personally, I didn’t really understand how to use exceptions until the first edition of Effective Java was published. But it’s been 15 years. That’s long enough for the message to get out. In 2010 we know better. Proper error handling requires distinguishing programming errors (runtime exceptions) from environmental problems (checked exceptions). Proper error handling requires correcting programming errors and writing handlers for unpreventable environmental conditions. Proper error handling requires knowing when to catch and knowing when to throw. If you try to work with only one kind of exception, or try to get by with only catch but no throws, then exceptions are going to seem very ugly and inconvenient. But if you use checked and runtime exceptions, and use catch and throws, then your error handling code will be far cleaner, safer, and more maintainable. Error handling without checked exceptions and throws is like arithmetic without * and /. Sure, you can do it, but why would when you when using the features the language offers makes life so much simpler?

61 Responses to “Bruce Eckel is Wrong”

  1. Sajal Dutta Says:

    The more unwanted/unexpected situations I can catch, the better! At least the program can have a graceful termination. It’s healthy from the end user perspective. PERIOD.

  2. Julian T Says:

    Interesting thread! One thing I’d add is that that checked exceptions sometimes don’t sit well with inexperienced and/or lazy coders who are using modern IDEs.

    I’ve come across a number of cases like this: the coder writes a line, the IDE underlines it with a red squiggle to tell the coder that an exception needs to be handled. Coder clicks on the helpful little lightbulb in the sidebar, IDE surrounds the code with try/catch and… bingo! The error goes away, but there’s nothing in the catch (maybe a log message if you’re lucky), and you have a swallowed exception.

    If I had a pound/dollar/euro for every time I’ve seen that in the last couple of years, I’d be able to buy myself a reasonable lunch. At least with runtime exceptions people don’t try to catch them and they end up crashing something…

    (Incidentally, does anyone know of any other languages that have implemented checked exceptions? I’d be interested to know…)

    jt

  3. Varun Chopra Says:

    Squirrelsewer Says:
    April 20th, 2010 at 6:39 pm
    For all of the checked exception fans: can you give us some idea of which Exceptions you commonly catch and handle? What % of checked exceptions are you able to handle?

    I agree with Squirrelsewer. Any code examples by Checked exception fans will be important for programmers to understand their point. Just repeating that programmers can handle errors and take alternate actions is not enough. Why so uncommon are the programmers taking alternate actions and what are they doing apart from logging or displaying error to the user?

  4. Eric S Says:

    People continue to repeat Anders’ comment about checked exceptions not scaling, though Gosling debunked this way back in 2003. I’ve worked on large Java systems and never seen this problem either. It only happens if programmers are using exceptions incorrectly, by passing all exceptions up to some top level by sticking them on the throws class without regard for whether they are exposing implemention details.

    Here’s the Gosling interview: http://www.artima.com/intv/solidP.html It’s funny, in this same article, he pretty much covered everything that has just been discussed here. There’s nothing new here. Why do we have to keep having this same argument? Personally I blame it on Java not having come with clear enough instructions about how to use exceptions properly; the arguments and explanations appeared after usage in the wild was already all over the place, and the seeds of confusion implanted in the culture.

  5. Eddie Says:

    @Bruce Eckel, your discussion of “the culture of Java” discusses the supporters of Java as if they are fundamentally different from the supporters of any other language or framework. 🙂 Unless by “the culture of Java” you specifically and narrowly refer to the culture of Sun (now Oracle) and you’re referring only to the architects, designers and implementers of the language itself. I thought I had seen some very clear discussions from folks at Sun in years past recognizing the errors made in AWT and the early collection classes, specifically. I have yet to run into a Java programmer who maintains that Java is absolutely perfect, with no problems or irritations. Anyway, I have yet to find any computer language that perfectly strikes the balance between all of the various tensions pulling on a computer language: ease of development, ease of support, simplicity, code bugs being caught early due to language features, etc. Have you? IMHO, there is no perfect balance.

    @others:

    Ultimately, you can write stable and reliable code in any language, even assembly, even shell script. The question is: “How much work is it?” How hard is it to find and fix the 1000 hour bugs? To what degree does the language or its toolkit or libraries help prevent the introduction of bugs? What happens when you swap 3rd party components with a newer version or different implementation?

    I lean on the side of “checked exceptions are a very good feature and have helped me write more robust code.” That said, I won’t argue that Java has a perfect implementation of them. When coding in Java vs C#, I have a *much* clearer picture of what can go wrong with a Java library, simply by examining the Exceptions that are thrown. I think that Anders’ argument about checked Exceptions not scaling is only true if you refuse to wrap checked Exceptions in other (checked or unchecked) Exceptions. Which is a silly choice to make. Wrapping exceptions is an essential part of maintaining an abstraction. You really don’t care, given an interface, that it can fail with this low level Exception or that one. But you do need to know which methods can fail by throwing an Exception. You will either know this because the language enforces it or through experience of the program failing when an uncaught exception is thrown. Which of those is preferable depends on many things, including the cost of failure, the cost of doing it right the first time, your unit test coverage, the documentation thoroughness of the 3rd party library, and so on.

    Why have other languages not implement Java checked exceptions? I think the choice of everything being unchecked is inferior and lazier and yet much easier to choose. I think the better choice — superior to both — hasn’t been implemented in a language yet. Checked exceptions, IMHO, are the superior choice of those two choices, but they come with a lot of baggage in their current implementation and are maybe not appropriate to all choices. This is why there are so many different kinds of languages out there in common use.

    Maybe what is needed is a method of automatically wrapping exceptions to match the current software layer, so a method could declare that any thrown exception that is not handled is automatically transformed into a different type appropriate to this layer of the abstraction. I agree with others who say, “Saying the *best* choice out of all options is that all exceptions are unchecked exceptions is like saying error checking code is not that important.” It is true that error checking code can add a big clutter to the code. But does that mean it’s better to go without it?

    Those who say that it’s best to catch Exceptions only at the top event loop … clearly don’t work on big enough systems, or at least they work on systems where it’s always OK to throw away a bad event or request. There are certainly realms where that is true enough. It is most definitely not true everywhere. It does not strike me as a surprise that where Java succeeded was at the enterprise level, not at the desktop or web level. I commonly have a try/catch around main event loop as a precautionary measure. I also commonly install an “uncaught exception handler” that logs so that I will always know about uncaught exceptions in 3rd party code. But this is defensive programming, not the solution.

    I think the ultimate solution to checked/unchecked will be found in some new language that will basically choose Java style checked + unchecked exceptions but with a significantly different twist, including a lot of syntactic sugar to solve the many valid points people have brought up about how annoying it can be to handle checked Exceptions. Things such as:

    1) Automatic exception wrapping so exceptions you don’t want to handle can bubble up without being exposed as the base exception they are. If you are a security 3rd party library and you fail because your configuration caused you to throw a MalformedURLException, you want to throw a SecurityConfigurationException that wraps the other so the programmer making use of your library has a proper abstraction.
    1a) Maybe something like try {} catch {} finally {} rethrows XXX where any Exception not explicitly caught is automatically wrapped as XXX, which can itself be checked or unchecked. Then you declare that this method throws XXX, and not whatever set of Exceptions could be wrapped by it, from that code block.
    2) Interface versioning so you can change an interface, changing what Exceptions are or are not declared to be thrown, with ways of handling mismatching interface versions. Maybe automatic wrapping can solve this problem.
    3) Perhaps the ability to mark a given Exception instance as checked or unchecked in a method’s declaration or code, so you can declaratively note whether something is expected to be caught or not. Maybe an annotation.
    4) More things. 🙂

    I disagree with many of Java’s choices with regards to what is a checked exception and what is not, and in both directions. I find, for example, that my code is more stable when I catch NumberFormatException, as this is almost always NOT a programming bug, but a bad input that needs to be handled. And I have found it appropriate in a very small number of places to catch Throwable, even if to log and rethrow, which as a general practice is not great but in the right place can provide invaluable information if you cannot depend on other software layers to report things appropriately. Also, “software should die on an Error being thrown” is not always true. If you get an out of memory error because the current request requires you to allocate an object that is too big to fit in memory, then you fail that request but don’t crash the program which is otherwise (in my experience) handling things just fine. It just got an invalid request. This depends on context and on whether or not one failed request means others can continue. How independent are the requests coming in? A programmer knows. An language runtime does not.

  6. Peca Says:

    The fact that this language feature has being discussed at such great length and with such opposing arguments, 15 years after its introduction, to me is an indication of a bad design choice. If the Java community is still struggling to properly understand the intent and proper usage of a feature, it should not have made the specification.

  7. dog Says:

    Peca’s argument is the only valid argument I’ve ever heard against checked exceptions. Bruce’s and others come from a habit of bad usage. As explained above there’s no reason your exception lists should grow or you should get leaky abstractions w/checked exceptions. In my over 12 years of Java I’ve had that problem. So I’m with Elliote here. But it is a regret that a lot of developers haven’t figured out how to properly take advantage of them. But I believe that is because most devs are lazy and rather not deal w/errors (which is the whole reason checked exceptions came to exist to start with).

  8. Rupert Frederick Says:

    Reading these comments hurts my brain. Those arguing against checked exceptions are arguing *for* bad API design. Failure modes *ARE PART OF YOUR API*. If your code can fail in a new way that was not anticipated by its API clients, then *YOU’VE BROKEN YOUR API*.

    Those arguing that you are “forced to catch exceptions you can’t handle” are arguing *for* being lazy bastards. You either declare that your method throws that exception (your IDE will do this for you!), or you spend the necessary time figuring out how what you’re calling can fail, and how that will affect your callers. Simply punting on that work because “it’s hard, waaaah” means that you have ill-defined API semantics because you’re just punting random errors to your callers.

    The way to use checked exceptions without a headache is easy:

    – Define a single abstract exception superclass for each subsystem.
    – Declare ‘throws’ using concrete subclasses, and DEFINE under what circumstances those subclasses will be thrown.
    – Translate underlying component’s exceptions into your component’s exception class when letting exceptions bubble out of your component.

    Period. That’s it. Your APIs will be stable, you can add new exceptions to your throw clauses, and you will have well-defined and easy to understand invariants on how, when, and why your APIs can fail.

    Ugh. People against checked-exceptions are just plain lazy bastards and their resultingly ugly code makes me angry. HULK SMASH.

  9. Chris L Says:

    Sorry, had to necro this mind-numbing thread.

    I honestly have no idea what kind of applications folks in the vehemently “pro checked exceptions” camp have worked on, but out here in the server-side development world they just don’t make any sense.

    As Rod Johnson (the godfather of Spring) and many other smart people realized a while back, the notion that you can actually recover from almost any checked exception is ludicrous. Your web app throws an exception while parsing, database access, I/O… guess what? The exception just gets logged, the client is notified, and you move on. That’s it. I’d love to hear some of the convoluted “recovery” scenarios these checked exception guys fantasize.

    So what your left with then is an app that either uses a framework (like Spring) that provides a rich unchecked exception hierarchy, you deal with service methods that throw 14 random things like ParseException because it’s thrown by some underlying dependency, or you cleverly “handle” your SQLException by, ummm, manually catching it and manually logging it right there.

    A robust hierarchy of well-documented RuntimeExceptions are perfect, because they allow you to catch the rare exception you may actually want to do something with, but disregard exceptions flowing through to the server boundary for logging 99.99% of the rest of the time. Honestly, it’d be a huge step forward for the Java API if they’d just simply make all exceptions unchecked (keeping the throws clauses intact on each interface for self-documentation). Adults are then free to choose.

  10. Anon. Y Says:

    @Chris

    I work in the server-side world and am rather confused by your irritation of checked exceptions. You say, “you deal with service methods that throw 14 random things like ParseException because it’s thrown by some underlying dependency, or you cleverly “handle” your SQLException”, but what kind of terrible API has 14 independent exceptions? How is anything you propose different from having a robust hierarchy of API level checked exceptions. You can choose to handle an exception by catching only the ones that matters or you can wrap the exception in your own API level exception. I feel that people are just avoiding the slight bit of effort it takes to generate a new exception for each level of abstraction.

  11. Lluis Says:

    Checked exceptions are Mordor. PERIOD.

Leave a Reply