Creator of the Apache Tapestry web application framework and the Apache HiveMind dependency injection container. Howard has been an active member of the Java community since 1997. He specializes in all things Tapestry, including on-site Tapestry training and mentoring, but has lately been spreading out into fun new areas including functional programming (with Clojure), and NodeJS. Howard is a DZone MVB and is not an employee of DZone and has posted 81 posts at DZone. You can read more from them at their website. View Full User Profile

The Tragedy Of Checked Exceptions

05.25.2011
| 7082 views |
  • submit to reddit

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 has just 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.

From http://tapestryjava.blogspot.com/2011/05/tragedy-of-checked-exceptions.html

Published at DZone with permission of Howard Lewis Ship, 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

Jonathan Fisher replied on Thu, 2011/05/26 - 12:31am

>>included truly experimental features, such as checked exceptions

I believe RuntimeExceptions didn't come till much later in the JDK (version 1.3 I think), so at first, everything was a checked exception. The oldest classes in the java API will throw checked exceptions where they shouldn't... tragic, yes.

Checked exceptions are extremely useful and should stay in Java. They're an extremely powerful part of an API to say indicate an alternate return flow that a caller must account for. However, they should only be used if there is reasonable suspicion that the caller can actually recover and retry. Generally, checked exceptions are best used in Business scenarios, whereas RuntimeException are best used in technical faults. JDBCException is probably not something you recover from and neither is MyAPIIsBrokenException. NonSufficientFundsException on your BankAccount.transfer() method is probably a reasonable checked exception.

Daniele Gariboldi replied on Thu, 2011/05/26 - 3:51am

I agree that checked exceptions are usefull mainly for business rules and alternate return flow. They are not usefull for low level errors. I find usefull that a method just returns a result, as used 90% of times, and alternate flows to manage. Checked exceptions if used well let the code be cleaner and less full of IF or SWITCH .....

Ricky Clarkson replied on Thu, 2011/05/26 - 4:31am in response to: Jonathan Fisher

"I believe RuntimeExceptions didn't come till much later in the JDK (version 1.3 I think)" Utterly wrong. They were present since JDK 1.0, which you can confirm by looking for "Since" in the API doc for RuntimeException.

Piero Sartini replied on Thu, 2011/05/26 - 4:35am

Yes - checked exceptions are very useful at a higher level. Another example is a login procedure: UserNotFoundException, PasswordInvalidException, etc.

One argument against: If a condition is expected, it may not be an exception and should be handled like any other case. But then you end up creating return objects which hold the information about what actions could be taken and if the method call was successfull,

I am not totally sure if exceptions are the way to go - it does not feel right to use them to implement business-logic. But the alternative does not feel right as well.

Adrian Engler replied on Thu, 2011/05/26 - 6:42am

There are low-level exceptions that can hardly be handled - the process has to be aborted, transactions rolled back etc.. For this scenario, unchecked exceptions should be used.

On the other hand, for actual workflow management when the "exceptional" situation is one that is expected in advance, generally, the case should be handled normally, not via an exception.

But isn't there somewhere in between, business exceptions that are on one hand really exceptional in the sense that what a method call was made for cannot be done, but on the other hand, they have a business sense and don't just mean "something didn't work", so that it makes sense if the API forces its users to deal with them? I think there are such cases.

Obviously, JDBC calls should not throw checked exceptions. But in some cases, it can be good to have the option of checked exceptions in business interfaces.

Michael Parmeley replied on Thu, 2011/05/26 - 9:45am in response to: Piero Sartini

Yes - checked exceptions are very useful at a higher level. Another example is a login procedure: UserNotFoundException, PasswordInvalidException, etc
I think your UserNotFoundException and PasswordInvalidException examples proves the author's point about exceptions being used when they aren't needed. There is nothing exceptional about a user not being found or a password not being found. The fact is that users quite often forgot or mistype their usernames and passwords so a user not existing or a password not matching isn't exceptional so neither of those cases should be handled with an exception.

Using exceptions in those cases has now just unnecessarily cluttered the code with exception handling code. When instead you could just populate a User object with a username and password, pass it to your business code that does authentication, then have isFound() and isPasswordValid() methods on your User object.

Zqudlyba Navis replied on Thu, 2011/05/26 - 4:36pm

I believe RuntimeExceptions didn't come till much later in the JDK (version 1.3 I think)


WTF? NullPointerException didn't exist in JDK 1.0?

Fab Mars replied on Fri, 2011/05/27 - 2:43pm

Oh come on not that discussion once again...

And the day exceptions won't be checked anymore we'll see as many posts like this asking for their return.

The amount of exceptions also depends on the programer and the application iteself. If there's unneeded exceptions or swallowing then some programmers should go back to school.

Henk De Boer replied on Fri, 2011/05/27 - 4:08pm in response to: Ricky Clarkson

Utterly wrong. They were present since JDK 1.0, which you can confirm by looking for "Since" in the API doc for RuntimeException.

Officially since 1.0 indeed, unofficially legend has it that this is one of the earliest exception types in Java, dating back to at least Oak and probably even before that. It explains the slightly peculiar name for this type of exception in relation to what it really does.

Henk De Boer replied on Sat, 2011/05/28 - 4:19am in response to: Michael Parmeley

There is nothing exceptional about a user not being found or a password not being found.

I don't 100% agree here. In modeling you would also call this the exceptional flow. In some kind of networks, IOExceptions happen rather often and therefor can't really be called exceptional. So should IOExceptions not be used there? Where do we draw the line exactly? Is it 1 exception in 10.000 packets? 100.000 packets? Any number?

Lund Wolfe replied on Sun, 2011/05/29 - 1:20pm

I also 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 senior 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.

Iain Shepherd replied on Wed, 2011/06/01 - 4:50am

It is bizarre to me how people defend checked exceptions. You guys are Java developers, right? You have written

  catch (FooException e) {
    throw new RuntimeException(e);
  }

approximately one million times, right? Did it help you?

Or perhaps you redeclare FooException in your own signature? Thus leaking implementation details to your caller?

Or, maybe you rethrow something else at a higher level of abstraction? Is it useful to your caller to know "something failed while doing foo" or "with the foo service"? Do they have any alternative strategy? Not usually. If anything their alternative is a retry, so they want to know if a retry is safe, for which they probably need the detail not the abstraction.

Checked exceptions discourage good practice while making work for everyone.

Balázs Bessenyei replied on Wed, 2011/06/01 - 2:12pm in response to: Iain Shepherd

No, I have never ever forced to do that, I would have been fired. If I would have ever tried something like that. 
(In your example the RuntimeException is an unchecked one, throwing that upwards is truly a bad idea.)

Exceptions should be demarcated on the borders of each layer. Allowing a lower layer exception to creep upwards is a capital offense by itself, in most cases.

The theory behind checked exceptions is that you need to deal with it on the spot (at least within the same layer) and not propagate it upwards or worse ignore it. Too bad it is too easy to misuse them.

Most cases returning NULL or a Default Instance is a way better idea. However in some case when the failure is caused by multiple reason, that needs to be handled differently, checked exceptions can be more expressive. Than polluting the caller code with additional conditions, to determine what was the cause of the failure. Which is error prone and usually gets forgotten to be updated or copy pasted all over the code base.

After all the called code already determined the cause of the failure, why waste resources in the client to determine the cause again. This is the part where most people get overzealous and ruin the day/week/month/year/life of every one else.

Sadly Checked Exceptions are an another example of a great idea, that turned inside out and twisted into an utter chaos and nightmare in practice, by wannabe idiots with lofty titles.

Declaring custom exceptions and re-wrapping existing low level exceptions then throw them to upper layers is usually a very bad idea. Yet, there are to many idiot wannabes out there, who thinks it is so cool to do it this way.

If the failures are handled the same way for each failure type then use NULL or Default Instance. If different conditions need different handling, encapsulate it into descriptive object, that can be easily processable by the caller. Without tricky/hacky custom logic on the caller side. Be it a checked exception or some other, hopefully non-clever (i.e. simple) solution.

Balázs Bessenyei replied on Wed, 2011/06/01 - 3:07pm in response to: Michael Parmeley

In this case there is a chance that the now initialized User object allowed to slip through. Like it is considered valid and authenticated, while it should not. Because of few additional features added to the user handling logic. Like the Disabling the User (show the appropriate error message), Password Expiry policy (redirect to the password change flow).

In both cases calling isFound() and isPasswordValid() would return true. So you need additional methods to determine the cause of the login problem. 
If you chose to update the above methods to return false instead. You still need to add the extra methods to determine the root cause and handle it differently.

So now the User object contains information about its detailed status. Also needs a complex code to determine its validity, which need to be constantly updated when new features are added. At least 2 nested IF structure. By your design, this code resides either in the caller code or in the User object, as isValid();

In the first case, it is very likely that someone won`t update this logic correctly. Or heaven forbids got copy pasted all over the place.
In the second case, the previously mentioned extra methods are still needed to execute the appropriate actions for the invalid user.

Using those exceptions seems a good idea (at first) to prevent error prone code duplication in the client code or increase the concerns of the User objects. After all the User validation logic already done that for the caller. Also those exceptions will force the caller to consciously deal with it. Sadly there are no way to prevent anyone from ignoring them or do some other utterly stupid things.

Unfortunately these are Custom Exceptions, which are extremely dangerous things (easy to get overzealous with them and/or misuse them) and usually a sign of bad design. Also in this case one would use the  exceptions as a primary control flow, which is an almost capital offense.

So both solutions (yours and the example) are questionable, unfortunately I don`t have significantly better either.

However, I would probably encapsulate the possible failure types in an enumerated type (Java ENUM) and return that (originally custom exceptions seemed such a good idea). Then use a switch to redirect the control flow appropriately. 

This design is also sub optimal and has shortcomings. Like does not force the caller to handle the failure cases. Does not force the client to handle newly added error conditions (which might actually be a good thing, the default case). Besides switch is also ugly, to some. 

But at least don`t duplicate logic in client side (or putting multiple concerns in the User object). Also avoids writing lots of custom exceptions and use them as a primary control flow.

With so many bad design out there, it is easy to understand why exceptions, especially checked, ones are hated.

Iain Shepherd replied on Fri, 2011/06/03 - 6:02am in response to: Balázs Bessenyei

(Ref to your first comment):

No result != failure. Any case of returning normally does not communicate that there was a failure, and requires extra checks scattered everywhere inside your caller, as few failure cases mean you should just carry on.

This is the insight behind structured exception handling in whatever language, including Java. If you disregard these conventions you are liable to introduce a lot of bugs, because the conventions are SO pervasive in Java that most clients will not even think to check.

Throwing a custom exception is often a bad idea, you are right there.

What's the alternative?

Generally, it is rare code that needs to care about specific failure cases. Yet checked exceptions force everyone to care. Almost all code should just clean up (if it can), and then let the caller know there was a failure.

Balázs Bessenyei replied on Fri, 2011/06/03 - 3:05pm in response to: Iain Shepherd

"No result != failure. Any case of returning normally does not communicate that there was a failure, and requires extra checks scattered everywhere inside your caller, as few failure cases mean you should just carry on."

True, thats the drawback of not using exceptions. Depends on the severity, a default object or null could be enough. Sometimes an exception is in order.

Lets assume you are having a Hotel Reservation site. Each hotel has has several attribute types, like room amenities, hotel amenities, room types, etc. You are retrieving each of this separately from each other from a data source.
Lets assume that a certain type of the Hotel owners usually, do not provide room amenities. So the resulting query to the data source, sometimes can`t find any room amenities.
Since it isn`t a fatal error, because the other Hotel data can be displayed and it can reserved. Also it is relatively a common occurrence. So this is not an exceptional event, so an empty object, is suffice.
Throwing a RoomAmenityNotFoundException would be an overkill. Also it would be swallowed or at most logged from the code.

On the other hand, if Hotel Reservation System, which is a web service on the local network, dies during the hotel reservation. Thats usually warrants a checked exception.

On the other hand failing to open a file in C, does not cause exception. It just return NULL, which needs to be checked, before accessing the File Pointer. This is a place where a properer checked exception would be in order.

"This is the insight behind structured exception handling in whatever language, including Java. If you disregard these conventions you are liable to introduce a lot of bugs, because the conventions are SO pervasive in Java that most clients will not even think to check."

Thats true, exceptions are have their place. I`m not against exceptions including checked ones. Actually I like the idea, that you are forced to deal with them as soon as possible. Both of my previous comments meant to be pro checked exceptions, but with critique on the common misuses.
Unfortunately, it is too easy to misuse them, like polluting the API with checked exceptions. 

For example, for a time the Go language, did not have an exception mechanism. Because of similar reasonings you have read above, in the blog post and comments. They had to realize that they need the facility after all.
|
In case of Java, converting a String to a number can result in a NumberFormatException, which is unchecked exception. Although it might have been better to declare that a checked one. Unfortunately it would have introduced a massive try-catch pollution in the client code. So most of the time programmers are just lucky to avoid error caused by this behavior.

"What's the alternative?

Generally, it is rare code that needs to care about specific failure cases. Yet checked exceptions force everyone to care. Almost all code should just clean up (if it can), and then let the caller know there was a failure."

Generally there are no alternatives except good design. The checked exception haters are usually remembering their frustration caused by idiotically designed and implemented code. Which is caused by overzealously overusing language features, where they shouldn't have used. But such is the life of programmer and it is typically human to try to ban them (the checked exceptions).

Using API and platform exceptions wherever possible and makes sense. Lower layer exceptions should be handled in the caller`s layer. Either by using correct handling solutions and cleanup. Unfortunately, in some cases you have to re-wrap it in an another custom business exception and throw it upwards.

Generally it depends on the situation whether the exception can be handled correctly in a layer or not
Unfortunately there are no universal guide lines for this. Just the definition

Generally exceptions should be used to indicate exceptionally rare failures, which needs to be handled in separate control flow than the normal case.

Kathy John replied on Tue, 2012/02/21 - 1:29pm

It's totally true I'd sad that for so many times and my colleges looked at me if I was a E.T.

Checked Exceptions are only useful for bad programmers

Great Post !!!

Comment viewing options

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