73. Checked vs Unchecked Exceptions
Checked exceptions must be declared or handled at compile time; unchecked exceptions (RuntimeException and its subclasses) do not. The design tradeoff: checked exceptions force callers to consider failure modes but pollute APIs.
Explanation
The Java compiler enforces that checked exceptions (those that extend Exception but not RuntimeException) are either caught in a try-catch or declared in the method's throws clause. This is Java's way of encoding "this operation can fail in a recoverable, caller-relevant way" directly in the type system.
Unchecked exceptions extend RuntimeException (e.g., NullPointerException, IllegalArgumentException, IndexOutOfBoundsException). They represent programming errors or conditions from which callers typically cannot recover โ they are not required to be declared or caught.
Design guidance (Effective Java): use checked exceptions for conditions from which the caller can reasonably be expected to recover. Use unchecked exceptions for programming errors. Avoid checked exceptions if you cannot provide useful recovery information. Many modern APIs (Spring, libraries, and modern Java itself) trend toward unchecked exceptions because checked exceptions compose poorly with streams and lambdas.
Example
import java.io.*;
import java.util.List;
public class CheckedVsUnchecked {
// Checked exception: caller must handle or declare
static String readFile(String path) throws IOException {
return new String(new FileInputStream(path).readAllBytes());
}
// Unchecked exception: documents the precondition, no throws clause needed
static int divide(int a, int b) {
if (b == 0) throw new IllegalArgumentException("Divisor cannot be zero");
return a / b;
}
// Modern pattern: wrap checked in unchecked for stream-friendly APIs
@FunctionalInterface
interface CheckedSupplier<T> {
T get() throws Exception;
}
static <T> T sneakyGet(CheckedSupplier<T> supplier) {
try { return supplier.get(); }
catch (Exception e) { throw new RuntimeException(e); }
}
public static void main(String[] args) {
// Checked: must handle
try {
String content = readFile("/nonexistent.txt");
} catch (IOException e) {
System.out.println("IO error (expected): " + e.getMessage());
}
// Unchecked: optional catch
try {
divide(10, 0);
} catch (IllegalArgumentException e) {
System.out.println("Caught unchecked: " + e.getMessage());
}
// Checked exception inside stream โ wrapper needed
List<String> paths = List.of("/tmp/a.txt", "/tmp/b.txt");
paths.stream()
.map(p -> sneakyGet(() -> readFile(p))) // wraps IOException
.forEach(System.out::println);
}
}Sources
- https://docs.oracle.com/javase/tutorial/essential/exceptions/catchOrDeclare.html โ Catch or declare tutorial
- https://www.pearson.com/en-us/subject-catalog/p/effective-java/P200000000138 โ Effective Java, Items 70-71