The inside scoop on what's happening with Tapestry ... from the creator of the Apache Tapestry framework. Plus all the normal, random thoughts on coding and technology.

Tapestry Training -- From The Source

Let me help you get your team up to speed in Tapestry ... fast. Visit howardlewisship.com for details on training, mentoring and support!

Tuesday, May 24, 2011

The Tragedy Of Checked Exceptions

If you ever get one of those interview questions along the lines of "What DON'T you like about Java?", I would hope that checked exceptions are at the top of your list. I think no other, ahem, feature, of Java has caused more code bloat, more problems, and less stability than checked exceptions. Java was the first main-stream language to include this concept (to my knowledge), the only (widely used) programming language that has it, and
I strongly hope it will be the last programming language to include it.

Checked exceptions cause a lot of grief. They often pollute APIs: look at how the JDBC API plays "cover your ass" by making every single method throws JDBCException regardless of which methods do any work that could possibly fail. At best, checked exceptions make sense only when there is a clear and documented way to recover from the exception (such as waiting and retrying the failed operation). In many cases, a simple boolean, indicating success or failure, would be much better than an exception, and accomplish the same goals with far less cost.

Checked exception's also encourage another terrible pattern: exception swallowing. "What do I do with this here MyAPIIsBrokenException? Well Eclipse just inserts code to print out the stack trace, so that's good enough." Thus real errors get discarded, and code that should break during testing slips through the cracks, causing nasty runtime failures and ominous messages to the console.

Really, what can you do with an exception? Either handle it locally and immediately, or wrap it in another exception, usually RuntimeException, and rethrow it ... but that approach is only effective if some higher layer does a good job of reporting the entire stack of exceptions, the way Tapestry does. More often, the exception just percolates up to a top-level loop and spews out a few hundred lines of glop onto the console or log.

I think part of the proof that checked exceptions are simply unworkable is the way throws Exception is creeping into standard APIs, such as the ones specified in project Coin (I'm thinking of Autocloseable). And what is the semantic value of throws Exception? It's useless ... because you are either going to log that exception to the console or wrap it in a new RuntimeException and re-throw it. So the authors of Autoocloseable have simply shifted work onto your lap (you get to write the code to catch it and rethrow it) when if they simply omitted the throws clause, and documented that "close() methods may throw a runtime exception" you could get the exact same effect, but write much less code.

I've also seen that checked exceptions have been a factor in the delays for JDK 8 Lambdas, complicating that specification much further than it needed to be, and forcing new and odder syntax into the language to accompany it.

Meanwhile, post-Java JVM languages ... including Groovy, Fantom, and Clojure ... simply ignore checked exceptions; which is easy enough to do as they are almost entirely a fiction of the Java compiler in the first place. You can write try...catch blocks in these languages, but there's no pressing need to, and application stability ends up being higher than in traditional Java code.

It is unfortunate that of all the ideas that Gosling, Joy, and folks had at the dawn of the Java language, they deferred ones we've really been missing (such as reified types and lambdas) and included truly experimental features, such as checked exceptions. But that's just hind-sight and second-guessing. The real tragedy is that, unlike (for example) JavaScript, with Java you can't just use the good parts. Instead, Java snares you with an almost irrational compulsion to preserve the early mistakes in the language, forever.

22 comments:

The silly thing is that checked exceptions only really make sense if you expect your client to be able to do something with them – in other words they are a control-flow construct. And as we all know, you shouldn't use exceptions for control flow!

The other thing they sort of give you is a disjunctive union return, but a very limited one. Fortunately there are much, much better options such as a true Disjunction like Scala/Haskell's Either.

As much as I agree with your sentiment, the one saving grace of declaring "throws Exception" is to explicitly prevent the useless wrapping by clients in caller-delegate style APIs, i.e. when the interaction is governed by a toplevel control loop. Declaring Exception (or in very rare cases more specific types) will allow the callee to explode properly without having to bend over backwards. This clearly does not work when the calee is performing arbitrary logic.

Other than that your observation is sadly more a reflection of Java "copy&paste" culture and its mindless propagation of bad ideas than anything else.

Add Scala to the list of post-Java JVM languages that ditch checked exceptions. It's notable because its static types system is generally more restrictive than Java's (e.g. arrays aren't covariant and it doesn't have "raw types"). Why did Scala ditch checked exceptions? For all the reasons you highlight and in particular for the havoc they play with higher order and generic programming (think lambdas, java.util.concurrent.Callable, every command pattern you've ever seen, etc).

Oddly, I don't think checked exceptions are necessarily an awful idea. But I do think it would be very hard work to design a language that made them un-awful. I'd lump it under the umbrella of research into effect systems and strongly advise any language designer targeting the mainstream to steer clear of them.

I can see I'm a minority, but I think checked exceptions can be useful - if used correctly. EncodingNotSupport should not have been a checked exception, but IOException is OK as a checked exception, you should deal with IO problems, socket timeouts, etc. But they should usually we wrapped in framework specific service exception (ServiceBusyException, ServiceUnavailableExcpetion) and they should be handled taking proper action, retrying, informing the user, etc.

No Tonny, you are not a minority! Checked exception is a best thing ever invented since OOP. I am not about to describe all the advantages of the subj, but I want to mention one.

If you refactor some code and investigate some new error case in a very deep subfunction, the developer did not thing about, it is a very best solution just adding a new checked exception.

It's a pity, that there are lot of developer who don't understand this powerful feature. It were also a lot of developer 15 years ago, who did not understand OOP. A boolean success flag was fine that time. Things are changing, but some people are often just to old to learn something new.

I also don't think checked exceptions are a bad idea. The problem is that it is difficult to know when to use them, and when not to.

You have to use a checked exception for situations that can legitimately arise. E.g. file not found. The programmer must deal with this ... otherwise the code is plain broken.

I agree with Jed --- they are like a union return. It's like saying the return value is either what you want, or an error. So, if you return a boolean or null to indicate an error --- well, that's the same thing.

>> Really, what can you do with an exception? Either handle it locally and immediately, or wrap it in another exception, usually RuntimeException, and rethrow it ... but that approach is only effective if some higher layer does a good job of reporting

That sounds like a rant against all possible exceptions. Do you want to get rid of all of them?

Anyway, we often show specific error message to user depending of what exception has been thrown. Exception class provides much more information then 'there was some problem'. What problem?

On uchecked/checked exceptions: it is very easy to miss unchecked exceptions from libraries or other modules. Making it unchecked does not make it disappear. Those then ends up on user screen.

What a post mate, you have blogged most of java developer's concern, nothing than checked exception pollute the code but I also like your solution approach to use boolean instead of checked exception just like old C and C++ does it. intention of Java API writer might be to enforce robustness of code and provides an opportunity to developer that this operation potentially fail and do you have backup plan etc but execution is not perfect.

Make mine number 3 to disagree. Checked exceptions do add complexity and they allow beginners to do the wrong thing in the wrong place (like just eating the exception), but they also make it obvious in code to even advanced developers that an exception needs to be dealt with at some point. Custom exceptions also can dramatically clarify and simplify the code.

This seems like a no brainer! If no other programming language has jumped on board, that's not Java's fault.

To clarify: I'm not specifically against exceptions, I'm against checked exceptions as part of an API signature. Most languages now a days have exceptions, and the concept of using exceptions to handle exceptional cases is valid. However, checked exceptions in Java have led to over-use of exceptions and APIs that "cover your ass" by declaring every method throwing exceptions, resulting in endless busy code to wrap those checked exceptions as runtime exceptions, or to ignore them (which is worse). What do I use in Tapestry? Either RuntimeException, or TapestryException (which subclasses RuntimeException and adds a Location property), or UnknownValueException (which subclasses RuntimeException with a list of valid values). If I want to handle exceptions, I do, but more typically, I pass them up to where they can be properly reported to the developer. I consider that the normal case, but checked exceptions require you to re-make that same decision on EVERY SINGLE API call.

Checked exceptions as part of APIs are really useful feature if applied correctly. You are showing bad code smells examples.

The part about boolean - you are kidding, right? If something bad happened, I want to know about it implicitly, because it usually means that rest of the executed code will be broken if the preceding statement failed. The discussion about exceptions vs. return values is like 10 years old...

Couldn't agree more with Howard. Disabling them should be a javac command line switch by now. In fact there are some workarounds - see project Lombok, adn this http://projectlombok.org/disableCheckedExceptions.html

It pollutes the API and either the calling method or the calling method's API (etc). Worse, if your code can throw a new exception then your contract should change.

I think the theory and practice of checked exceptions are out of sync. In theory, checked exceptions should be rarely used (such as IOExceptions on code that can actually throw such an exception). In practice, they are used in "cover-your-ass" mode. Given that overuse of checked exceptions becomes a strange attractor, the correct thing is not to put them in the language in the first place. But that horse has left the barn ... maybe Java 2100 (long after we're all dead) will fix this :-)

The problem I see here is that a majority of programmers who write apis don't seem to understand the purpose of checked exceptions. There are also many lazy programmers who just use "throws exceptions" to avoid having to write something meaningfull. I suggest Exception be made abstract. If you want to throw one, sub-class it. The exception should have a meaning worthy being made a checked exception.

The other solution is a long, long shot which is to educate Java programmers on which type of exception to use and when. Due to Java's popularity we get a lot of programmers that just learnt to do it via some 3 month course they took and aren't really interested in programming, they just want a paycheck. That is just my 2 cents.