Skip to content
Free public beta until December 4, 2026: every course is open, no card needed. After that, the first chapter of each course stays free and full access is a one-time $39 ($19 for up to 100 founding members chosen for helpful feedback). Your progress is kept. How the beta works

Every way to handle a failure

Catch, declare, wrap, clean up, or let it propagate: the exception tools and the strategy that picks between them.

Ask whether this method can do something useful about the failure.

  • It can recover (retry, use a default, ask again) → catch the specific exception right here.
  • It can't, and the failure is checked → declare it with throws, or wrap it in an exception that fits this layer, passing the original as the cause.
  • Something must be closed whatever happens (a file, a connection) → try-with-resources, not a finally you write yourself.
  • It's a bug (null, a bad index) → don't catch it; fix the code. Let one top-level handler log it.
  • The program ended with a trace → read it from the top for the type and message, then down to the first line in your own code.

The mechanics

Throwing interrupts the method and unwinds the stack.

Which clause runs, and where execution resumes.

The block of a try statement that runs on every way out: normal end, caught or uncaught exception, or return.

Runs whether or not an exception was thrown, even after a return.

Formal. Every exception is a Throwable. Error and RuntimeException, with all their subclasses, are unchecked. Every other Throwable (for example IOException and InterruptedException) is checked: if a statement can throw a checked exception, its method must either catch it or declare it with a throws clause. The compiler checks this one method at a time, from method headers.

In plain English. Some failures, like a missing file, are a normal part of the world, and Java makes you write down a plan for them: handle it here, or warn your callers in the method's header. Programming mistakes, like a null where an object should be, aren't checked; the fix is to correct the code.

Example. Files.readAllLines(path) is declared throws IOException, so calling it without a try/catch or a throws IOException on the enclosing method fails to compile with unreported exception IOException; must be caught or declared to be thrown. Integer.parseInt("x") throws the unchecked NumberFormatException, and needs neither.

Why this rule exists. It makes the recoverable failures a method can cause part of its signature, so callers can't forget them.

The compiler's catch-or-declare rule.

An exception the compiler makes you catch or declare, such as IOException.

Must be caught or declared, for failures a correct program can't rule out.

An exception the compiler doesn't make you catch or declare: a RuntimeException or an Error.

A RuntimeException or Error; usually a bug to fix.

Passing it on

How an exception travels up the call stack.

Catching an exception and throwing a new one that holds the first as its cause.

Example

throw new StorageException("save failed", e);

Wrapping keeps the original as the cause.

An exception type for your layer, with the cause attached.

Cleaning up and deciding

A try statement that declares resources in brackets and closes each one automatically, on every path.

Closes every resource, in reverse order, even when the block throws.

Where to catch, what to log, and the habits that hide failures.

From the header to the first frame in your own code.