Checked exceptions: Java’s biggest mistake

Checked exceptions have always been a controversial feature of the Java language.

Advocates claim they ensure checking & recovery from failures. Detractors say “catch” blocks can almost never recover from an exception, and are a frequent source of mistakes.

Meanwhile, Java 8 and lambdas are here. Are checked exceptions becoming obsolete in the Java world?

The Intent of Checked Exceptions

In the mid 90’s, James Gosling at Sun came up with a new language.

At the time, C++ programming required every single function return to be checked for error. He decided there had to be a better way, and built the concept of “exceptions” into Java.

The intent of checked exceptions was to locally flag, and force developers to handle, possible exceptions. Checked exceptions have to be declared on a method signature, or handled.

This was intended to encourage software reliability & resilience. There was an intent to “recover” from contingencies – predictable outcomes other than success, such as InsufficientFundsException on attempting a payment. There was less clarity, as to what “recovery” actually entailed.

Runtime exceptions were also included in Java. Since null pointers, data errors, and illegal states/ accesses could occur anywhere in code, these were made subtypes of RuntimeException.

Runtime exceptions can be thrown anywhere, without requiring to be declared, and are much more convenient. But would it be correct to use them instead?

The Drawbacks

The crucial point here, is that runtime & checked exceptions are functionally equivalent. There is no handling or recovery which checked exceptions can do, that runtime exceptions can’t.

The biggest argument against “checked” exceptions is that most exceptions can’t be fixed. The simple fact is, we don’t own the code/ subsystem that broke. We can’t see the implementation, we’re not responsible for it, and can’t fix it.

Particularly problematic were the areas of JDBC (SQLException) and RMI for EJB (RemoteException). Rather than identifying fixable contingencies as per the original “checked exception” concept, these forced pervasive systemic reliability issues, not actually fixable, to be widely declared.

For any method, the possibility of failure includes all sub-methods called by it. Potential failures accumulate up the call tree. Declaring these on method signatures no longer offers a specific & local highlight for the developer to watch for – declared exceptions spread throughout the call tree.

Most EJB developers have experienced this – declared exceptions become required on methods through the tier, or entire codebase. Calling a method with different exceptions requires dozens of methods to be adjusted.

Many developers were told to catch low-level exceptions, and rethrow them again as higher (application-level) checked exceptions. This required vast numbers – 2000 per project, upwards – of non-functional “catch-throw” blocks.

Swallowing exceptions, concealing the cause, double logging, and returning ‘null’/ uninitialized data all became common. Most projects could count 600+ mis-coded or outright errors.

Eventually, developers rebelled against the vast numbers of “catch” blocks, and the source of error these had become.

Checked Exceptions – incompatible with Functional Coding

And then we get to Java 8, with its new functional programming features – such as lambdas, Streams, and function composition.

These features are built on generics – parameter & return types are genericized, so that iteration & stream operations ( forEach, map, flatMap) can be written which perform a common operation, regardless of item type.

Unlike data types, however, declared exceptions can’t be genericized.

There is no possibility in Java to provide a stream operation (like, for example,  Stream.map) which takes a lambda declaring some checked exception, & transparently passes that same checked exception to surrounding code.

This has always been a major points against checked exceptions – all intervening code, between a throw and the receiving “catch” block, is forced to be aware of exceptions.

The workaround, of “wrapping” it in a RuntimeException, conceals the original type of the exception – rendering the exception-specific “catch” blocks envisaged in the original concept useless.

Finally we can capture Java’s new philosophy in a nutshell, by noting that none of the new “functional interfaces” in Java 8 declare checked exceptions.

Conclusion

Exceptions in Java provided major benefits in reliability & error-handling over earlier languages. Java enabled reliable server & business software, in a way C/ C++ never could.

Checked exceptions were, in their original form, an attempt to handle contingencies rather than failures. The laudable goal was to highlight specific predictable points (unable to connect, file not found, etc) & ensure developers handled these.

What was never included in the original concept, was to force a vast range of systemic & unrecoverable failures to be declared. These failures were never correct to be declared as checked exceptions.

Failures are generally possible in code, and EJB, web & Swing/AWT containers already cater for this by providing an outermost “failed request” exception-handler. The most basic correct strategy is to rollback the transaction & return an error.

Runtime exceptions allow any exception-handling possible with checked exceptions, but avoid restrictive coding restraints. This simplifies coding & makes it easier to follow best practice of throw early, catch late where exceptions are handled at the outermost/ highest possible level.

Leading Java frameworks and influences have now definitively moved away from checked exceptions. Spring, Hibernate and modern Java frameworks/ vendors use only runtime exceptions, and this convenience is a major factor in their popularity.

Personalities such Josh Bloch (Java Collections framework), Rod Johnson, Anders Hejlsberg (father of C#), Gavin King and Stephen Colebourn (JodaTime) have all come out against checked exceptions.

Now, in Java 8, lambdas are the fundamental step forward. These language features abstract the “flow of control” from functional operations within. As we’ve seen, this makes checked exceptions & the requirement to “declare or handle immediately” obsolete.

For developers, it is always important to pay attention to reliability & diagnose likely points of failure (contingencies) such as file open, database connection, etc. If we provide good error messages at this points, we will have created self-diagnosing software – a pinnacle of engineering achievement.

But we should do this with unchecked exceptions, and if we have to rethrow, should always use RuntimeException or an app-specific subclass.

As Stephen Colebourn says, if your projects are still using or advocating checked exceptions, your skills are 5-10 years out date. Java has moved on.

How are you dealing with exceptions & reliability? Add your thoughts now.

References:
Oracle: Barry Ruzek, Effective Java Exceptions
Jacob Jenkov: Checked or Unchecked Exceptions
Google Testing blog:  Checked exceptions, you have to go
– Ander Hejlsberg on checked exceptions
 Stephen Colebourne: Remove checked exceptions from Java

Counter-argument:  James Gosling
James Gosling on checked exceptions

17 thoughts on “Checked exceptions: Java’s biggest mistake”

  1. As a C++ developer I am jealous of Java’s checked exceptions. Compile-time verification of error handling seems like a wonderful idea. With unchecked exception anything that’s thrown is at the mercy of a developer reading the documentation and hopefully catching it somewhere. In other words it is no better than returning an int error code- actually it’s worse.

    1. Well, unchecked exceptions already free you from the need to check return-codes. So in that sense, they’re the big step forward you’re looking for.

      For checked exceptions to be useful, however, depends on selective & usefully handleable (recoverable) exceptions being the only ones checked. You could propose that FileNotFound or failures connecting could be in that basket — but I/O failures reading or writing once the file is open, are almost completely not.

      Unfortunately the Java library designers got this boundary completely wrong. So there you go. There could plausibly have been a place for a selective & useful sprinkling of checked exceptions, but everybody’s been burnt now & nobody wants a bar of this.

      Modern Java frameworks & other languages (C#) have now gone the other direction, and moved away from checked exceptions entirely. So, no need to be jealous!

      Thanks again for your input.

      1. You say, “Unfortunately the Java library designers got this boundary completely wrong”

        That doesn’t mean that checked exceptions are evil. You propose not to use checked exceptions AT ALL, but there are some situations where checked exceptions are appropriate.

        You seem to think there are only 2 options: Use checked exceptions everywhere, or use them not at all.

        A few examples where checked exceptions are wise:
        Integer.parseInt(String s) throws NumberFormatException
        DateFormat.parse(String s) throws ParseException
        URL(String spec) throws MalformedURLException

        Also, don’t forget that files should be closed, even if a failure occurs while reading them, so a try/finally or try-with-resources block makes sense:

        try (FileReader fr = new FileReader(“E://file.txt”)) {
        char [] a = new char[50];
        fr.read(a);
        for(char c : a) System.out.print(c);
        } catch (IOException e) {
        }

        If IOException was unchecked, the developer could easily forget to catch it.

        1. Thanks Ludwig! I agree the input-parsing scenarios you suggest are good examples of usefully handleable (and often recoverable) usecases, where the checked exception pattern could have value.

          However the problem with checked exceptions, is their spread to vast numbers of places where they cannot — in the vast majority of cases — be usefully handled or recovered.

          For example JDBC (SQLException), RMI for EJB (RemoteException), and IOException — apart from initial opening of connections & resources, programs commonly are pervasively dependent on these underlying capabilities.

          Should operations on already-open resources or connections fail, there is little useful handling/ recovery that most programs can do. Burdening every single database operation or backend call with purposeless boilerplate that couldn’t actually solve the problem was, in the opinion of many, a fundamental mistake.

          Based on Java’s experiences modern frameworks such as Spring, and other languages such as C#, have steered away from using checked exceptions.

          Thanks for your input!

  2. Java 8 streams are a great step forward and I use them extensively now but Oracle are still introducing API’s with checked-exceptions: e.g. java.nio.file.Files::lines(...) throws IOException

    I found this trying to write a simple LOC program using Java 8


    Files
    .walk(path)
    .filter(p -> p.getFileName().endsWith(".java"))
    .forEach(p -> {
    try {
    System.out.println(p.getFileName() + ", " + Files.newBufferedReader(p).lines().count());
    }
    catch (IOException e) {
    e.printStackTrace();
    }
    });

    That try/catch is required and is butt ugly 🙁

    1. Thanks David, it sure is! Look, I have some sympathy for the idea of “opening files” or “establishing connections” as checked exceptions — those bits can definitely be expected to fail sometimes (contingency & possibly recoverable).

      My biggest beef is with the Java library mis-design of using concrete inheritance and making *every* I/O operation and remote invocation subsequent to establishing the connection checked.

      There’s no informative or reliability benefit from that, as failures can occur unpredictably at any time due to lower level transports/ technical issues — the very definition of a runtime exception 🙂

      I agree it’s ironic that Java 8 is still adding APIs with checked exceptions! I think they’ve gone too far up the creek & have to preserve consistency with existing library.

      There’s a lot of concern to maintain backwards compatibility; and, even if they did want to consider they might have got it wrong, can’t change without breaking that 🙁

      Thanks for your comment!

      1. A function either works or it doesn’t. That’s a Boolean. Everything else is bullshit. That’s why we had to invent Junit and all the mocking frameworks.

        1. The alternative in C was to check the error-code on every function call. This was intrusive code of little value — since most failures couldn’t be recovered, and had to either return -1 or panic.

          Exceptions provide a better mechanism; so long as ‘checkedness’ isn’t used to create its own intrusive anti-pattern.

    2. If there was an unchecked exception thrown instead, and you did not handle it in your forEach parameter, the behaviour would be different and it would not be possible to implement the behaviour of this code (i.e. continuing to try to count lines in the rest of the files).

  3. Checked exceptions are a big mistake, but I would argue that adding annotations was an even bigger mistake. Annotations were not supposed to change the run-time behavior of code, but that’s exactly how they’re used by major projects like Spring, and that makes it almost impossible to understand what code will run without either very good documentation or lots of time to spend in a debugger.

  4. I think that checked exception have their place, but they should be a sub-class of the Exception class (which shouldn’t be checked). There would be no need for a weird RuntimeException, because Exception would fulfill that role.

    Something like

    class Failure extends Exception {…}
    class IOFailure extends Failure {…}
    … and so on.

    this way the developer could decide to hadle failures explicitly or have a catch all block for failures only, while unchecked exceptions could go up the chain freely, as they (usually) should.

    Also, every failable operation should provide a non failable alternative — something like tryParseInt(…) which wouldn’t fail (even though it could throw non-checked exceptions, like everything else).

Leave a Reply

Your email address will not be published. Required fields are marked *