W3docs

Java Exceptions

An overview of exceptions in Java — what they are, the exception hierarchy, and why exception handling matters.

An exception is what happens when your program runs into a situation it can't handle on its current path — a file that isn't there, a number divided by zero, an array index that's out of bounds. Instead of returning a wrong answer or silently corrupting state, Java stops the current method, builds an exception object describing what went wrong, and starts looking for code that knows how to deal with it. Learning the exception machinery is learning how Java tells you what went wrong, where, and what you might do about it.

What an exception actually is

An exception is a regular Java object. Specifically, an instance of a class that inherits from java.lang.Throwable. When something goes wrong, the JVM (or your own code) creates one of these objects and throws it. From that moment on, the program follows a different control flow until either a catch block accepts the exception or the thread terminates.

The object carries information: a type (the class — NullPointerException, IOException, etc.), a message (a human-readable description), and a stack trace (the call chain frozen at the moment the exception was created). When you read an exception in a console, you're reading those three things.

Exception in thread "main" java.lang.ArithmeticException: / by zero
  at calc.Money.divide(Money.java:42)
  at calc.App.main(App.java:11)

The first line is type + message. The indented lines are the stack trace, newest call first.

Why exceptions instead of error codes

Older languages — C is the classic example — signal failure with return codes: every function returns an int, you check it, and if it's negative something went wrong. That approach has two problems:

  1. You can ignore the return value. A caller that forgets to check it sees no immediate failure, and the bug surfaces later in a confusing place.
  2. The error path clutters the happy path. Every line of real work is wrapped in if (err) return err;.

Exceptions invert this. The default is that an unhandled exception stops execution, loudly. You opt in to handling it where you actually have a strategy. The good path stays clean; the recovery path is in its own block.

The three things that can go wrong

Java sorts everything Throwable into three buckets, and the difference matters because the language treats them differently:

  • Error — the JVM itself is in trouble. OutOfMemoryError, StackOverflowError. You don't catch these in normal code. There's usually nothing useful you can do.
  • RuntimeException (a subclass of Exception) — programming bugs that show up at runtime. NullPointerException, IndexOutOfBoundsException, ClassCastException. The compiler doesn't force you to handle them, because in correct code they shouldn't happen.
  • Checked Exception (everything else under Exception) — recoverable failures the program should anticipate. IOException, SQLException. The compiler requires you to either catch them or declare them in your method's throws clause.

The line between "programming bug" (runtime exception) and "anticipated failure" (checked exception) is one of Java's most argued-about design decisions. We'll come back to it in Java checked vs. unchecked exceptions.

How throwing and catching work

When a throw runs, the JVM:

  1. Unwinds the stack — the current method exits abruptly, then its caller does the same, and so on.
  2. At each frame, it checks for a try { ... } catch (SomeType e) { ... } block whose catch matches the exception's type (or a supertype).
  3. The first matching catch wins. Control jumps there, the stack stops unwinding, and execution resumes in the catch block.
  4. If nothing matches, the thread dies. In a single-threaded program, that means the JVM prints the stack trace and exits.

This is why a thrown exception can travel through many methods before it's caught — every uncaught throw bubbles up another frame.

A first taste

You don't have to write throw to encounter an exception — Java itself throws them when something goes wrong. Here's the simplest example: dividing an integer by zero. The JVM creates an ArithmeticException for you.

java— editable, runs on the server

Without the try/catch, the third iteration would crash the whole program and the fourth would never execute. With it, the failure is contained: we get one error message and the loop continues. That ability — to bound the damage of a failure — is the whole point of the exception machinery.

What this part of the book covers

The remaining chapters in this part are the working vocabulary of Java exception handling, one piece at a time:

  • The shape of try/catch/finally and what each clause is for.
  • Multiple catches and the multi-catch shortcut.
  • try-with-resources for anything that needs closing.
  • Throwing exceptions yourself with throw, and declaring them with throws.
  • Checked vs. unchecked: which one to use and when.
  • The full class hierarchy.
  • Writing your own exception types for your domain.
  • The principles that distinguish good exception handling from defensive noise.

Read it in order — each chapter assumes the previous one.

What's next

Exception handling starts with the most fundamental construct: the try/catch block. Continue to Java try...catch.

Practice

Practice
A method `loadConfig()` reads a file and throws `IOException` on failure. The caller wraps the call in `try { loadConfig(); } catch (RuntimeException e) { ... }`. The IO fails. What happens?
A method `loadConfig()` reads a file and throws `IOException` on failure. The caller wraps the call in `try { loadConfig(); } catch (RuntimeException e) { ... }`. The IO fails. What happens?
Was this page helpful?