- 20 Reasons we should avoid Checked Exceptions
- Language facts
- Trending backwards
- Dependencies
- Contracts/Interfaces
- Inconsistencies
- Tools
- Code bloat
- Modern libraries
- More info
- These older languages adopted other features, but not Checked Exceptions
- No Checked Exceptions in these popular languages:
- These languages learned from the Checked Exception mistake
- No Checked Exceptions in these popular languages:
- C# (microsoft)
- Swift (Apple)
- Golang (Google)
- Kotlin (JetBrains & Google)
- Javascript (Mozilla)
- Typescript (microsoft)
- Rust (Mozilla)
- VB.net (microsoft)
- PHP
- Ruby
- Groovy
- F# (microsoft)
- Dart (Google)
- Objective-C (Apple)
- OCaml
- The most popular non-java jvm language, backed by Google & JetBrains
- The preferred language for Android development
- The most popular functional language on jvm
- The most popular jvm scripting language
- Another popular functional language for jvm
- TIOBE Index measures programming language popularity
- Exactly 1 out of the 20 most used programming languages has Checked Exceptions (java)
- For backward compatibility, they cannot be removed from Java
- Other JVM languages are free to avoid Checked Exceptions
Item-05: Checked Exceptions are incompatible with Java 8+ Functional interfaces
- Checked Exceptions are incompatible with modern Java
- Incompatible interfaces include:
- Including, (but not limited to):
- Reactor
- Backed by Spring
- See their recommended workaround for the Checked Exception problem
- RxJava
- Akka Reactive Streams
- Reactor
- Only a problem for Checked Exceptions (since they must be imported)
- The import requirement creates a compile time reference
- Typically, you are forced to add a
<dependency>so method signatures compile - Also called "Higher coupling"
- Encapsulation failure
- Consider a CRUD abstraction over your data layer, throwing SQLException
- What if you want to swap out a NoSQL store or a cache?
- What if clients drive their logic based on thrown SQLException?
- Once you adopt Checked Exceptions, you're stuck with them
- Removing them can break client code that catches a Checked Exception
- As a library author, inability to make (safe) changes is undesirable
- Engineers must select from the terrible options below (see Item-16)
- Engineers may opt for alternatives (or fork your lib to fix it)
- Even the authors of Java try to avoid CheckedExceptions
- See java.net.URI::create
- See their policy/guidance
- See Violation for URISyntaxException
- See Violation for CloneNotSupportedException
- See Violation for NumberFormatException
- See Closeable vs AutoCloseable
- Even worse, Compare how InterruptedException and IOException are handled for both of these
- Try-catch blocks are biased toward only catching Checked Exceptions
- And ignoring (possibly) more important RuntimeExceptions
- By default:
try {
legacyMethod(); // throws Checked Exception
} catch ( SomeCheckedException ex ) {
ex.printStackTrace(); // <--- this is the Error hiding anti-pattern
// Notice the caller never has a chance to handle
}- Compiler forces you to acknowledge
- It doesn't force you to handle cleanly
- It doesn't force you to handle RuntimeExceptions
- This is the Error Hiding anti-pattern
try {
legacyMethod(); // throws Checked Exception
} catch ( SomeCheckedException ex ) {
// this is the error hiding anti-pattern
}- Propagation spreads the problem to ALL of your callers
- Propagation forces you to change your method signatures in ways unrelated to your code
- Wrapper code is worthless
- Wrapper code hinders both comprehension & performance
- Wrapper code increases the indentation of your methods
- reduces comprehension
- increases maintenance cost
- If you control both caller & caller, the simpler solution is to use RuntimeException in the callee
try {
legacyMethod(); // throws Checked Exception
} catch ( SomeCheckedException ex ) {
throw new RuntimeException(ex); // wraping
}- In practice, the caller is RARELY in a position to deal with the exception
- eg. What to do when IOException thrown while closing a Connection?
- Exceptions are propagated up to a Component that CAN handle it.
- This means you're using Options 1,2,3 above until you get to the real handler
- This pollutes your method signatures
- This forces all callers of those intermediate methods to do the same
- Has 7 unchecked exceptions
- Has 2 Checked Exceptions, both extend IOException (to align with existing libs)
- Has Throwables utilities to fix Checked Exceptions
- lang3 has a utility to fix Checked Exceptions