Chapter 52: Checked vs Unchecked Exception in Java
Java splits throwable problems into two families for compile-time rules. This chapter names them calmly: checked means the compiler watches whether you declared or handled; unchecked means you may throw or catch, but the compiler does not force a plan.
throw was Chapter 50; throws was Chapter 51. Custom types land in Chapter 53.
1. Topic title
Checked: compiler-enforced plan. Unchecked: optional handling, still serious
2. What it means
Unchecked types inherit RuntimeException (or Error, which you should almost never catch). Examples: IllegalArgumentException, NullPointerException, ArithmeticException. You can throws them for documentation, but callers are not required to catch or throws by the compiler.
Checked types inherit Exception but not RuntimeException. Example: java.io.IOException. If your method can let one escape, you either catch it or add throws IOException (Chapter 51). Callers repeat the choice up the stack.
3. Why the split exists
The language designers wanted recoverable external failures (disk, network) to stay visible in signatures. Programming mistakes were treated as bugs you fix rather than annotate on every method.
Modern teams disagree on how helpful that is—still, you must recognize the split to read Java libraries.
4. Quick contrast table (conceptual)
| Idea | Checked | Unchecked |
|---|---|---|
| Typical examples | IO-style failures you might retry | Bad arguments, broken invariants |
| Compiler | expects catch or throws chain |
does not |
| API feel | noisy signatures | lighter signatures |
5. Java code example
class Gate {
void admit(String ticket) throws java.io.IOException {
if (ticket == null) {
throw new IllegalArgumentException("ticket required");
}
if (ticket.isBlank()) {
throw new java.io.IOException("reader could not scan barcode");
}
}
}
public class CheckedUncheckedDemo {
public static void main(String[] args) {
Gate g = new Gate();
try {
g.admit(" ");
} catch (java.io.IOException ex) {
System.out.println("checked path: " + ex.getMessage());
}
try {
g.admit(null);
} catch (IllegalArgumentException ex) {
System.out.println("unchecked path: " + ex.getMessage());
}
}
}admit declares throws IOException because IOException is checked and may throw from the blank-ticket branch. IllegalArgumentException needs no throws clause.
6. Explanation of code
main must catch IOException (or declare throws IOException) because it calls admit. The null call never reaches IOException; it throws IllegalArgumentException, which main catches by choice, not by compiler force.
7. Common mistakes
Catching Exception to silence both families—you lose signal about what you are willing to recover from.
Declaring throws Exception on public APIs—callers cannot specialize their handling.
Assuming unchecked means “ignore it”—uncaught RuntimeException still stops the thread just like checked ones.
8. Best practices
When designing your own types (Chapter 53), default toward unchecked for caller bugs and checked only when recovery is common and well-defined.
9. Small practice task
Temporarily remove the try/catch around g.admit(" ") and watch the compiler error. Then add throws IOException to main instead of catch and confirm the project compiles—notice how the responsibility moved upward.
Beginner tip
If your IDE highlights a method call in red and mentions “unreported exception”, you met a checked type. Hover, read the type name, then choose catch (local recovery) or throws (delegate).