Q10 of 40 · Core Java
What is the difference between checked and unchecked exceptions?
Short answer
Short answer: Checked exceptions extend Exception (not RuntimeException) and the compiler forces callers to handle or declare them. Unchecked exceptions extend RuntimeException and require no declaration. Checked = recoverable I/O or API contracts; unchecked = programming errors the caller shouldn't be expected to recover from.
Detail
Java's exception hierarchy splits at Exception. Everything under RuntimeException (and Error) is unchecked — the compiler doesn't require catch or throws. Everything else under Exception is checked — the compiler enforces that callers either catch it or propagate it via throws.
Checked exceptions (IOException, SQLException, InterruptedException) represent conditions external to the program that a well-written caller should anticipate: a file might not exist, a DB might be down, a thread might be interrupted. The compiler forces acknowledgement — you can't accidentally ignore an IOException from a file read.
Unchecked exceptions (NullPointerException, IllegalArgumentException, IndexOutOfBoundsException, ClassCastException) represent programming errors: you passed null where it wasn't allowed, an index was out of range, a cast was wrong. These are bugs in the code, not expected runtime conditions. Forcing callers to catch NullPointerException everywhere would make code unreadable.
Modern Java trend: many frameworks (Spring, JUnit, most test libraries) wrap checked exceptions in unchecked ones to reduce boilerplate. Lombok's @SneakyThrows and Stream's inability to accept methods that throw checked exceptions have pushed many teams toward unchecked-first designs. The debate is ongoing, but in test code the convention is clear: test methods declare throws Exception at most, and assertion failures are unchecked.
Error is a third category — OutOfMemoryError, StackOverflowError — representing JVM-level failures. Never catch Error unless you're writing a framework.
// EXAMPLE
ExceptionHierarchy.java
// Hierarchy
Throwable
├── Error (unchecked — JVM failures, don't catch)
│ ├── OutOfMemoryError
│ └── StackOverflowError
└── Exception
├── IOException (checked — must handle or declare)
├── SQLException (checked)
├── InterruptedException (checked)
└── RuntimeException (unchecked — no compiler enforcement)
├── NullPointerException
├── IllegalArgumentException
├── IllegalStateException
└── IndexOutOfBoundsException
// Checked: compiler forces you to acknowledge it
try {
String content = Files.readString(Path.of("data.json")); // throws IOException
} catch (IOException e) {
throw new RuntimeException("Could not load test data", e); // wrap as unchecked
}
// Unchecked: no declaration needed
public void setRetries(int n) {
if (n < 0) throw new IllegalArgumentException("retries must be >= 0, got " + n);
}
// Wrapping pattern used in Streams (Stream can't propagate checked exceptions)
List<String> lines = paths.stream()
.map(p -> {
try { return Files.readString(p); }
catch (IOException e) { throw new UncheckedIOException(e); }
})
.toList();