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

8 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.

  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!

Leave a Reply to David Cancel reply

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

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>