Checked vs Unchecked Exceptions in Java

Every Java exception is either checked or unchecked. The distinction is enforced by the compiler: checked exceptions must be either catched or declared in a throws clause; unchecked exceptions can be thrown freely.

The hierarchy

Throwable
β”œβ”€β”€ Error                           β†’ unchecked (don't catch)
β”œβ”€β”€ Exception                       β†’ CHECKED
β”‚   β”œβ”€β”€ IOException                 β†’ checked
β”‚   β”œβ”€β”€ SQLException                β†’ checked
β”‚   └── RuntimeException            β†’ UNCHECKED
β”‚       β”œβ”€β”€ NullPointerException
β”‚       β”œβ”€β”€ IllegalArgumentException
β”‚       └── ClassCastException

Checked β€” the caller must acknowledge

public String read(Path p) throws IOException {   // declared
    return Files.readString(p);
}

// Caller is forced to choose:
try                       { read(p); }
catch (IOException e)     { log.error("...", e); }
// OR declare it upwards:
public void handle() throws IOException { read(p); }

Unchecked β€” can be thrown anywhere

public void setAge(int age) {
    if (age < 0) throw new IllegalArgumentException("age < 0");
    this.age = age;
}

Which to use

SituationPick
I/O, network, DB errors the caller might recover fromChecked (IOException, custom)
Programming bug (null, bad argument, bad state)Unchecked (RuntimeException, IllegalArgumentException)
Business errors in an application (order not found)Unchecked (trend toward)
JVM problems (OOM, StackOverflow)Error β€” don't catch

The checked-exception debate

Modern Java tends toward unchecked exceptions:

  • Spring, Hibernate, Lombok wrap checked exceptions as unchecked.
  • Lambdas (stream().map(...)) can't throw checked exceptions β€” they force you to wrap.
  • Kotlin has no checked exceptions at all.

Wrapping checked β†’ unchecked in lambdas

// ❌ Doesn't compile β€” readString throws IOException
paths.stream().map(Files::readString).toList();

// βœ… Wrap
paths.stream().map(p -> {
    try   { return Files.readString(p); }
    catch (IOException e) { throw new UncheckedIOException(e); }
}).toList();

Common mistakes

  • Declaring throws Exception β€” leaks the abstraction; callers catch everything. Be specific.
  • Converting unchecked to checked β€” bugs aren't recoverable. Let them propagate.
  • Swallowing checked exceptions with an empty catch β€” the compiler's whole point is defeated.

Related

Pillar: Java exceptions. Siblings: throw, throws, Custom exceptions.