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 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 (
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.
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.
– 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