Q10 of 40 · Core Java

What is the difference between checked and unchecked exceptions?

Core JavaJuniorjava-exceptionschecked-exceptionsunchecked-exceptionsexception-hierarchy

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();

// WHAT INTERVIEWERS LOOK FOR

Correct hierarchy (RuntimeException = unchecked, everything else under Exception = checked, Error = separate), the design rationale for each category, and awareness of the modern trend toward unchecked wrappers in frameworks and streams.

// COMMON PITFALL

Saying Error is checked. It's not — it's a separate branch of Throwable that you almost never catch. Also, candidates sometimes say 'checked exceptions must always be caught' but you can also declare them with throws and let the caller deal with them.