Thursday, 23 September 2010

Checked Exceptions (#bijava)

In my recent post I brought up the idea of a backwards incompatible version of Java. I indicated that checked exceptions would be an obvious candidate for removal.

Checked exceptions

Java exceptions are divided into checked and unchecked. This distinction is not terribly neat in the API, because RuntimeException extends Exception. The purpose behind the distinction between the two isn't overly clear from the Javadoc:

RuntimeException is the superclass of those 
 exceptions that can be thrown during the normal operation of the 
 Java Virtual Machine. 
 A method is not required to declare in its throws 
 clause any subclasses of RuntimeException that might 
 be thrown during the execution of the method but not caught. 

 The class Exception and its subclasses are a form of 
 Throwable that indicates conditions that a reasonable 
 application might want to catch.

Maybe this lack of clear purpose didn't help developers use exceptions in the way intended (because the intended way wasn't specified).

In my last post, I summarised my views as follows:

If you are still using or advocating checked exceptions then I'm afraid you skill set is 5 to 10 years out of date.

I base this opinion on observation of the industry, and the behaviour of its most active participants.

Key industry projects do not use checked exceptions. Spring has a specific policy of wrapping any checked exception to make it unchecked. Hibernate changed from checked to unchecked - in the words of a key committer I spoke to at JavaOne "we threw our hands up and said sorry" about using checked exceptions. Another key player in Java EE confirmed that that specification is also moving away from checked exceptions.

But since there are some who have commented that they thought these weren't sufficient data points, I've gathered more information. I walked around the entire exhibition floor at JavaOne and asked each vendor what their Java API used. (Apologies in advance to any vendor where the information is inaccurate!)

These 10 vendors indicated that they expose only runtime exceptions:
Spring Source, Terracotta, JReport, Neo4J, Electric Cloud, Serva, Exo, n-software, Ricoh, Sahi, Splunk.

These 8 vendors indicated that they expose some checked exceptions, typically including the JDK exceptions like IOException or MalformedURLException:
JRebel, Artifactory, Caucho (limited by J2EE spec), JetBrains, Parasoft, JBoss (partially limited by J2EE spec), JRapid

These 5 vendors indicated that they expose a number of their checked exceptions, including ones they have written, like FooCompanyException:
Windward reports, Sonatype, Perforce (have own NPE for example), Blackberry, Intersystems Cache (all exceptions extend Throwable directly)

A number of other companies weren't able to provide the info, or didn't have an exposed public API.

So, there is no there is no single viewpoint by the vendors. Many now only use runtime exceptions, with others only really exposing JDK checked exceptions. However some have a wide selection of checked exceptions.

Do these data points change my analysis?
No.

In addition to finding out what a company did, I also asked a number of people the "what if" question of removing them from JDK 9 (#bijava). Most were in favour of removing them, with those having C# experience distinctly in favour. As always, there were some who still favoured checked.

The checked exception issue will continue to be emotive. For me its a classic case of not considering the community effect as being important enough.

My take is that far too often you will see a checked exception caught and ignored, or simply print stack trace and continue. Developers are human, and they are lazy. No amount of telling developers the right way to do something is ever going to change that. Developers will always use them inappropriately.

For me, a language feature that is proven to be used incorrectly most of the time is a language feature not worth having, however logical the basic idea may seem.

Summary

My view is that broken features in the Java language should be removed. And checked exceptions are broken because in 15 years we've found it impossible to get developers to use them properly (even the JDK library has many poor examples).

Like any religious debate, I don't expect to change minds overnight. But I ask those justifying them to look a little closer at how in 15 years, we're still using them poorly. What does that say?

Feedback welcome as always! Please use #bijava to talk about backwards incompatible Java on twitter.

27 comments:

  1. I think the proposal must not be to remove checked exceptions from Java, instead it should be change some exception types to be non checked when appropriate. All the examples you give of libraries that do not advocate or use checked exceptions are tools/frameworks, not business logic. I understand that having things like IOException everywhere as a checked exception is too much, but I find very useful to have a business method forcing the client to test the exception (or at least telling the developer they must do it always).

    ReplyDelete
  2. I think there are some cases where checked exceptions are justified. Right now I can think of two: contingencies (in the sense of this article http://crmondemand.oracle.com/technetwork/articles/entarch/effective-exceptions-092345.html) and internal use in a library.

    What about a new approach to checked exceptions where they are distinguished from unchecked ones not by their type, but rather by the way they are thrown. For example, "throw e" would throw a checked exception, while "raise e" would throw an unchecked one.

    Plus an easy way to change a checked exception to unchecked one, e.g. the one proposed by Robert Elliot in the comment to your recent article (http://blog.lidalia.org.uk/2010/01/checked-and-unchecked-exceptions.html).

    Examples:

    void foo() {
    raise new Exception(); // unchecked
    }

    void bar() throws Exception {
    throw new Exception(); // checked
    }

    void baz() {
    @Unchecked(Exception.class)
    bar(); // checked exception changed to unchecked
    }

    Note that @Unchecked is easier to write than an empty catch block, thus eliminates this practice. And it is way more readable.

    If a public API contains a wrongly used checked exception, the client can easily change it to unchecked.
    Further, if the designer of that API evaluates that the use of checked exception was a mistake, she can change it to unchecked without a change in exception's ancestry, thus not breaking any client code.

    If the use of checked exceptions in public APIs is still considered wrong in any case, the throws clauses could be restricted to protected (or package private) methods.

    Anyway, even if there is will to adopt this new approach to checked exceptions, their removal will be the first step.

    ReplyDelete
  3. Lawrence Kesteloot23 September 2010 at 19:53

    It's not enough to assert that some people make mistakes with checked exceptions. You also have to consider the mistakes made because of unchecked exceptions. This includes things you should have caught but didn't think to, and things you caught but you shouldn't have (because the method didn't throw it). A good example of the latter is the habit of catching Spring's EmptyResultDataAccessException when calling JdbcTemplate.query(), but query() just returns an empty list. I see that one a lot, where the developer's special handling of an empty set is never run.

    ReplyDelete
  4. I'm enjoying this series on #bijava. I'm strongly in favor of eliminating checked exceptions and primitives, and less clear on nullable types.

    One consideration I don't see you emphasizing in the articles so far, Stephen, is a comparison with other surging languages right now. In the projects I've been involved in, more and more companies are using ruby and python instead of java, and it seems to me that any proposal for a #bijava is directly or indirectly motivated by trying to keep up with these other competitive languages.

    I just realized that I'm not giving you enough credit for your brief callout to c#, and encourage you to do more of this. I find it compelling that neither python, ruby, nor c# use checked exceptions. And while each is a flawed language in its own right, collectively they seem to be doing many things right that java could learn from.

    ReplyDelete
  5. After 12 years of Java development, I still believe like Robert that checked exceptions make a lot of sense if used wisely.

    But I'm sick and tired of dealing with checked IllegalAccessExceptions, InstantiationExceptions, InvocationTargetExceptions, MalformedURLExceptions, SAXExceptions, ParserConfigurationExceptions, TransformerConfigurationExceptions and URISyntaxExceptions, just to name a few of the most prominent evildoers.

    It would be interesting to estimate the total amount of characters which have been flooding the logs of Java servers in the last 15 years world-wide, just because silly checked exceptions have forced developers to catch, chain and rethrow them.

    Bijava should completely redefine the exception hierarchy.

    ReplyDelete
  6. Lawrence Kesteloot23 September 2010 at 20:41

    Karl, the one that annoys me the most is UnsupportedEncodingException when I'm passing in a hard-coded encoding name that's guaranteed to exist by the language ("UTF-8", "ASCII", etc.). I have to catch and re-throw as an Error.

    ReplyDelete
  7. This sounds like you are falling to the lowest common denominator. Your logic appears to be that many developers misuse checked exception, therefor it should not be available to any developers.

    Is Java to be a language of choice for the best developers or the "lazy" ones?

    There are many features of Java which are mis-used or very poorly understood, e.g. floating point arithmatic. By the same logic we should remove this too. e.g. I recently had a candidate of 10 years IT experience tell me that -0.0 might have a round error. :P

    ReplyDelete
  8. Lawrence Kesteloot23 September 2010 at 21:28

    Speaking of Java enhancements (though not a backward-incompatible one), I'd like:

    if (one.!equals(two))

    be sugar for:

    if (!one.equals(two))

    It reads better that way, and it's usually after typing "one" that I realize that I want not-equals. (Obviously for any boolean method, not just equals.)

    ReplyDelete
  9. Can a checked exception fan out there provide an example of checked exceptions as they should be used? Just a short sample where you think checked exceptions really shine.

    ReplyDelete
  10. @Lawrence: Oh yes, UnsupportedEncodingException is a top-notch killjoy!

    @Squirrels Ewer: Think of a service API for the creation of user accounts. A UserAlreadyExistsException is a good candidate for a checked exception, since it can be handled properly. Nobody can tell in advance if the chosen username already exists, so you have to deal with it anyway.

    Rule of thumb: Checked if truly recoverable under regular conditions. Unchecked for all stuff that will end up as stack-trace in the server logs or as "Woah dude, something bad happended!"-dialog at the client.

    I know, this distinction remains vague and squishy. But in many cases it's a simple question of common sense. I have never seen any piece of Java software which seriously attempts to recover from a TransformerConfigurationException (beyond logging, wrapping and rethrowing, until a final UncaughtExceptionHandler does some brute-force error handling).

    ReplyDelete
  11. @Karl - ok, what did you have in mind for recovering from UserAlreadyExistsException? I'd like to build out the entire example.

    ReplyDelete
  12. I posted the original blog entry to the java posse google group and it turned into a giant checked exception debate. I of course am in the checked exceptions are retarded camp. Checked exceptions lead to bad code, it's that simple.

    Here is the link: http://groups.google.com/group/javaposse/browse_thread/thread/6ab9c4feb095435e

    ReplyDelete
  13. Hi Stephen,

    I'm a (lazy) developer and I catch checked exceptions only to call a global exceptions handler (to report myself the error basically).

    If you remove checked exception, how can I define that exception handler globally ?

    ReplyDelete
  14. Writing code that is interoperable with Java and does not have checked exceptions is proven to work with Scala. (There is some fine print like annotating checked exception). In the end checked exceptions are a compiler hack.

    But removing checked exceptions might be to fast. The goal of the checked exception could be changed from forcing everyone to handle checked exceptions to supporting the developers that would like to handle the exceptions.

    If you have a language that supports pattern matching the compiler may give you a warning when a pattern matching is not exhaustive. The same could be supported for exceptions. Basically, leave the checked exceptions (semantics) as they are now. If a developer wants to catch exceptions he can state if you would like to catch all (checked or run-time) exceptions. The compiler can then warn if you did not handle all checked exceptions.

    try{
    [..]
    }
    exhaustive //compiler warning if other exceptions than E1 and E2 are thrown
    catch (E1) { }
    catch (E2) { }

    The distinction between fatal (throwable), handleable (checked) and programming errors (run-time) is still useful.

    ReplyDelete
  15. If a few people understand checked exceptions, why do we have to remove it completely? That doesn't quite make sense.

    I believe that checked exceptions should be rare, but they should be allowed because it can be useful in some cases, such as operating with low level operations.

    Such as File Systems, Networking, etc.

    ReplyDelete
  16. Checked exceptions were useful in the absence of static-analysis tools that can provide the same (or better) information.

    One might conclude from a reading of the JLS that all exceptions are ideally checked, but certain were excluded because they would detract from program readability or because their inclusion was questionable since they cannot or should not be handled. Not only does this have the compiler performing an analysis task (one among many) that, arguably, is conflating its purpose, but checked exceptions have several drawbacks, including detracting from program readability and encouraging a variety of anti-patterns.

    Checked exceptions is a feature that should be removed from the language syntax and moved from compilation to static analysis.

    ReplyDelete
  17. Artur Biesiadowski27 September 2010 at 09:37

    I'm wondering - is removing checked exceptions really backwards incompatible change?

    Let's assume that we just corrected javac that it does not require any catch statements ever. You can still use 'throws' clause, for every kind of Throwable, as documentation tool. You can try to catch any exception, regardless if it was declared in throws clause in any of the method calls inside the block.

    Is there any current code which would be broken by those changes? As no jvm change is required, all old class files should also run in new java.

    ReplyDelete
  18. You can't do static analysis of exception handling in applications where the exception generating code is dynamically loaded (and unknown at analysis time). For example if using a plugable file system, you may wish to handle exceptions like FileNotFound or FileLocked, but without throws statements on the interfaces the static analysis can't help.
    Further, without checked exceptions, those throws lists will tend to be inaccurate (those lazy developers).

    In short checked exceptions are intended to support static analysis of exception handling whether in javac or elsewhere. Without them, exceptions become a purely dynamic feature. Many will be happy with that --- they never handle specifc exceptions anyway, but just have a generic "it failed" handler at the top of the stack.

    ReplyDelete
  19. "What did you have in mind for recovering from UserAlreadyExistsException?"

    Well, it would make sense to inform the user that the name selected for the new account is already taken... and to ask him to choose another. Basically what every registration form does.

    You will want to handle this exception specifically. Just showing a dialog saying: "Oops, something went wrong: username not available." is dumb. It is clearly a recoverable, common business error that is part of the normal flow.

    "Checked exceptions should be rare, but they should be allowed because it can be useful in some cases, such as operating with low level operations. Such as File Systems, Networking, etc."

    No, that's exactly where you should *not* use them. We don't want to have to catch DiskFull and OutOfMemory and NetworkDown exceptions every other method. Those should be runtime exceptions that we only catch in a catch-all, log-and-give-up block.

    It's recoverable business errors that are solvable (and mostly caused by) the user that should be checked exceptions... if any.

    I have a love hate affair with checked exceptions. When used wisely I think there is a strong case for them as they make the exceptions a part of the method signature just like the return value. It's all the over-use that spoiled it.

    So maybe runtime exceptions should be the default and checked exceptions should be the exceptions?

    ReplyDelete
  20. I agree with you that checked exception are the pain in the ass of every Java developer.
    But I think that most of the problems they present is related the ackward syntax, and not in the check mechanism itself, which could be useful in some cases.
    That's why I was happy to hear from Mark Reinhold that some improvements are coming on that side, but not more.

    ReplyDelete
  21. Very interesting discussion, Stephen.

    Couldn't resist putting in my 2c. (Also posted to my blog).

    It seems to me that declaring a checked exception involves making three claims:
    1. You can't programmatically prevent this exception from occurring. You must recover from the exception rather than prevent it.
    2. You're so likely to forget to handle this case, and it is likely enough and serious enough to occur, that I'm going to force you to decide how you're going to deal with it right now ...
    3. ... but it's not so terminal or abnormal that most applications shouldn't try to catch it. (That's Error.)

    Item 1 makes me think they should be rare. Prevention is better than cure, but checked exceptions require you to write cure.

    But I'm not sure that "rare" is the same as "non-existant"

    ReplyDelete
  22. IMHO, the original problem is separation of concerns.

    Let's take an example. There are, for example, general purpose packages (~ use context agnostic packages), like the Java "Collection" package. When mixing such packages with other API with specific exceptions, the heaven would be that those specific exceptions would have no impact on the use of the general purpose packages, with an ideal separation of concerns. But such ideal do not exist (yet?)

    Then, 2 solutions appear, following an all-or-nothing approach:
    - all: force always developers to deal explicitly with exceptions
    - nothing: make exceptions silent, that is, unchecked.


    The ideal would be to associate exceptions with a kind of namespace. According to that idea, Java classes interested into some events may declare themselves interested into exceptions with given namespaces, that is, may declare they are ok to deal with exceptions of some given namespaces; then, for such classes, those exceptions, possibly raised by called methods, would become checked exceptions, while other exceptions would be unchecked.

    At first glance, it looks like doable, but the need may not worth the effort, while there is a simplest solution: making all exceptions silent!

    ReplyDelete
  23. Thanks for your thoughts in an area I've given much thought over the years. Two things occur to me about what you've written. Firstly the case where programmers misuse checked exceptions doesn't seem like a good reason to call that part of the language flawed; it almost never happens in the large codebase I work on, and when it does, usually by lazy programmers, then they are educated as to how important considering these details are and they realise they need to apply more thought to their work. Secondly I agree with Robert above, where a library (such as SpringJdbc) chooses to use unchecked exceptions exclusively that fact that it is a library surely comes into making that decision, thus this is not really a justfication for using unchecked exceptions exclusively across the board. My own opinion is that a strategy consisting of a mix of both types of exception is a reasonable and effective strategy, the important point being that the strategy used in an application is just that, a considered strategy used consistently throughout the application. That said, a strategy including exclusive use of unchecked exceptions is also a reasonable approach, the one I'm highly dubious of (and have seen) is exclusive use of checked exceptions, now that is something I do strongly disagree with.

    ReplyDelete
  24. I am struck by the symmetries between the suggestions of nullable types and checked exceptions.

    Each is a case of asking the compiler to keep track of additional information to enforce better safety. Each is a case in which documentation alone would likely prove insufficient.

    The argument for nullable types is that it would reduce boilerplate a little and cut down on innate, unguarded errors by a large measure. The argument against checked exceptions is that it would reduce boilerplate a lot, even if it increases the number of unguarded errors a little.

    Mr. Colebourne rejected "Maybe" as a potential solution for nullable types, because of the functional mindset that entails and the verbosity it entails. I wonder, though, if the consequence of eliminating checked exceptions would be the creation of many "Maybe" classes. That is, in the case of an exception which should be handled by the calling method, methods would have to be defined with a return type that encapsulates the exception, the return value, and perhaps also various descriptions of the returned state. (Leading to oft-ignored return values for methods that would otherwise return void.)

    Perhaps, though, this "returned Maybe" construction would be rare enough in practice that it would reduce boilerplate overall and be worth it. Or perhaps a better solution would be include annotations like "@ignore(ExceptionClass)" and "@rethrow(ExceptionClass, AsExceptionClass)"; together, those two annotations would handle much of the boilerplate.

    So far, I'm mostly disagreeing with the people asserting that it's not worth having the discussion. It's clearly a trade-off with consequences each direction.

    ReplyDelete
  25. I will keep it short:
    - This post is not an analysis but rather an opinion;
    - I agree with many of the previous posters that checked exceptions are misused rather than unneeded and that one can do as much harm with unchecked exception;
    - Stating "If you are still using or advocating checked exceptions then I'm afraid you skill set is 5 to 10 years out of date." sounds to me rather arrogant and, in the light of what you showed after, a clear contradiction.

    Alessandro

    ReplyDelete
  26. Good article! Checked exceptions were originally intended to highlight contingencies, but were always incompatible with best-practice "throw early, catch late" exception-handling/ and FP functional programming constructs.

    Instead of being being used solely for contingencies, though, they became used for all sorts of unrecoverable low-level systemic failure. This was the fundamental mis-use in Java libraries, which saw them become such a problem.

    See: http://literatejava.com/uncategorized/checked-exceptions-javas-biggest-mistake/

    ReplyDelete
  27. checked exceptions can be a (literal) life saver in safety-critical software.

    ReplyDelete