Exception Handling
Every prior chapter assumed things go right. This one covers what Java does when they don't — and a genuinely distinct position between two approaches already covered on this site.
try/catch/finally — The Basics
Code that might fail goes in try; a specific exception type is handled in catch; finally runs unconditionally, whether an exception was thrown or not.
The Exception Hierarchy
Every exception is a Throwable. Below that: Error (serious JVM-level problems, not meant to be caught) and Exception, which splits again into RuntimeException and everything else — that split is exactly where checked and unchecked diverge.
Checked Exceptions — Compiler-Enforced Acknowledgment
A checked exception — anything that's an Exception but not a RuntimeException, like IOException — must be either caught or declared with throws in the method signature. The compiler enforces this and refuses to compile otherwise. This is genuinely new territory versus C++: cpp2-6's own C++ exceptions carry no compiler-enforced acknowledgment requirement at all — a function that throws can be called with zero handling and zero declaration, compiling cleanly, only to crash at runtime if the exception is never caught anywhere up the call stack.
Unchecked Exceptions
RuntimeException and its subclasses — NullPointerException, ArrayIndexOutOfBoundsException, ArithmeticException — carry no such requirement. They can be thrown and left completely unhandled, compiling fine, just like every exception in cpp2-6's C++ model. Unchecked exceptions are typically reserved for programming errors — bugs — rather than expected, recoverable failure conditions.
The Real Middle Ground vs. Rust's Result<T, E>
Rust encodes failure directly in a function's return type — the signature itself says "this can fail," and the caller must handle the Result via match, ?, or an explicit unwrap() or the code won't compile clean. Java's checked exceptions force a genuinely comparable acknowledgment, but through a side-channel — the throws clause — rather than the return type itself. It's a real middle position: more compiler enforcement than C++ ever offers, but less structurally integrated into the type system than Rust's approach.
try-with-resources
Any resource implementing AutoCloseable can be declared in the try(...) parentheses — its close() is called automatically once the block exits, exception or not. It's Java's own answer to the resource-cleanup problem cpp1-5's RAII solves via destructors and Rust's Drop solves automatically — genuinely less automatic than either, since it still requires the explicit try(...) syntax rather than firing on scope exit alone.
| Approach | C++ (cpp2-6) | Java checked | Java unchecked | Rust |
|---|---|---|---|---|
| Compiler-enforced handling | no | yes — catch or declare | no | yes — built into the return type |
| Where the requirement lives | nowhere | throws clause (side-channel) | nowhere | the function signature itself |
| Resource cleanup | RAII — automatic on scope exit | try-with-resources — automatic within the block, syntax required | Drop — automatic on scope exit | |
Exception type hides which failures a piece of code actually expects and silently swallows unrelated bugs along with it — catch the narrowest type that's genuinely recoverable.
catch (Exception e) {} compiles fine and looks like handling — but it silently discards every failure, including ones that indicate a genuine bug elsewhere. At minimum, log or rethrow; never swallow silently.
Coding Challenges
Write a method that deliberately divides by zero inside a try block, catch ArithmeticException specifically, and use a finally block to print a message that always runs regardless.
📄 View solutionWrite a method that calls Files.readAllBytes without catching IOException and without declaring throws. Show the resulting compile error, then fix it using throws in the method signature.
📄 View solutionWrite a class implementing AutoCloseable with a close() method that prints a message, then use it in a try-with-resources block alongside code that throws an exception, showing that close() still runs.
📄 View solutionChapter 7 Quick Reference
- try/catch/finally — finally always runs, exception or not
- Checked exceptions (Exception minus RuntimeException) must be caught or declared with throws — compiler-enforced, unlike C++'s cpp2-6 model
- Unchecked exceptions (RuntimeException and subclasses) carry no such requirement — same as C++'s exceptions
- Java's checked exceptions are a real middle ground: more enforcement than C++, less structurally integrated than Rust's Result<T, E> in the return type itself
- try-with-resources auto-closes any AutoCloseable — Java's own, less-automatic answer to RAII/Drop
- Next chapter: the Collections Framework — Java's own answer to cpp2-2's STL containers