skip to content

Describe the Throwable class hierarchy in Java. What are its two main branches and what does each represent?

level: juniorimportance: must knowfreq 75%

answer

  1. Throwable = root; only it can be thrown/caught
  2. Two branches: Error vs Exception
  3. RuntimeException lives inside Exception
  4. Error + RuntimeException = unchecked; rest of Exception = checked
  5. Error = JVM/system; RuntimeException = bugs; checked = recoverable

basics

~20 s

Throwable is the root of everything you can throw or catch. It splits into Error (serious system problems you shouldn't catch, like running out of memory) and Exception (problems your program can handle, like a missing file or bad input).

solid answer

~40 s

Throwable sits at the top of the tree; only Throwable and its subclasses can be thrown with throw or caught with catch. It has two direct subclasses. Error represents serious problems usually raised by the JVM or environment that an application is not expected to recover from, such as OutOfMemoryError or StackOverflowError. Exception represents conditions a reasonable application might want to catch and handle, such as IOException. Within Exception sits a special sub-branch, RuntimeException, for programming errors like NullPointerException or IllegalArgumentException. The Error/RuntimeException side is unchecked (the compiler doesn't force handling), while the rest of Exception is checked (the compiler forces you to catch or declare it).

go deeper

for a junior

Can draw the basic tree: Throwable at the top, Error and Exception below it, and knows only Throwable subclasses can be thrown or caught.

for a middle

Correctly places RuntimeException under Exception, maps the checked/unchecked boundary onto the tree, and gives at least one right example in each category.

for a senior

Explains the design rationale — why JVM problems (Error), programming bugs (RuntimeException), and recoverable conditions (checked Exception) are separated — and the compile-time nature of the checked rule.

for a principal

Connects the hierarchy to API and library design: choosing checked vs unchecked for custom exceptions, the readability/coupling trade-offs, and how the split influences error-handling strategy across a large codebase.

## What problem is this solving? When something goes wrong during a program's execution, Java needs a uniform way to signal that failure and a uniform way to respond to it. The mechanism is *exceptions*: an object that describes the failure is created and "thrown", which interrupts normal flow and unwinds the call stack until some code "catches" it. For this to work, every throwable thing must share a common type so that `throw` and `catch` know what they operate on. That common type is **`java.lang.Throwable`**. ## The rule: only Throwable can be thrown or caught The `throw` statement and `catch` clause only accept a `Throwable` (or a subclass). You cannot `throw "oops"` or `catch (String e)`. Every error object in Java is therefore an instance of some class that ultimately extends `Throwable`. ## The two branches `Throwable` has exactly two direct subclasses, and they encode a *contract about who is expected to handle the failure*: ``` Throwable ├── Error (serious, do NOT catch — usually unrecoverable) └── Exception (application conditions — usually recoverable) └── RuntimeException (programming bugs — unchecked) ``` ### Error **`Error`** signals serious problems that a normal application *should not try to catch* because it generally cannot do anything useful about them. These are typically raised by the JVM or the runtime environment, not by your own business logic. Examples: `OutOfMemoryError` (the heap is exhausted), `StackOverflowError` (recursion went too deep and the call stack filled up), `NoClassDefFoundError` (a class that was present at compile time is missing at run time), `AssertionError` (a Java `assert` failed). When an `Error` propagates to the top of a thread, that thread typically dies. ### Exception **`Exception`** signals conditions that a well-written application might reasonably anticipate and recover from — a file that isn't there (`IOException`/`FileNotFoundException`), a network timeout, a parse failure. The intent is that you catch these and do something sensible (retry, show an error, use a default). ### RuntimeException — a sub-branch of Exception Inside `Exception` lives **`RuntimeException`**. These represent *programming mistakes* — situations that, in principle, correct code would have prevented. Examples: `NullPointerException` (dereferencing a null), `IllegalArgumentException` (a method got an argument it forbids), `IllegalStateException` (a method called when the object is in the wrong state), `ClassCastException` (a bad cast), `ArrayIndexOutOfBoundsException`, `NumberFormatException` (e.g. `Integer.parseInt("abc")`), `ConcurrentModificationException` (modifying a collection while iterating it). ## Checked vs unchecked — why the split matters The tree shape drives Java's **checked-exception** rule, enforced by the *compiler* (not the JVM): - **Checked**: everything under `Exception` *except* `RuntimeException` and its subclasses. The compiler forces the calling code to either `catch` it or declare it with `throws`. Forgetting to do so is a compile error. - **Unchecked**: `RuntimeException` (and subclasses) **and** `Error` (and subclasses). The compiler imposes no handling requirement — you *may* catch them but are never *forced* to. So the dividing line for "does the compiler force me to handle it" is: are you a `RuntimeException` or an `Error`? If yes → unchecked. The design intent: programming bugs (`RuntimeException`) and catastrophic JVM problems (`Error`) shouldn't litter every method signature with `throws`, whereas genuinely recoverable, expected conditions (checked `Exception`) should be visible in the API. ## How a learner at each level should hold this Junior: name the root (`Throwable`) and the two branches, and that only Throwable can be thrown/caught. Middle: map checked/unchecked onto the tree and give correct examples in each box. Senior: explain *why* the design separates JVM problems, programming bugs, and recoverable conditions. Principal: reason about API design implications (when to choose checked vs unchecked for your own exceptions).

  • Can you catch a Throwable directly, and is it a good idea?
    Yes — catch (Throwable t) compiles and catches everything including Errors. It's almost always a bad idea because it swallows OutOfMemoryError, StackOverflowError, and similar unrecoverable problems you can't meaningfully handle, and can hide programming bugs. Catch the narrowest type you can actually handle.
  • Where does RuntimeException sit relative to Exception and Error?
    RuntimeException extends Exception, so it is a sub-branch of the Exception side, not a third top-level child of Throwable. Error is the other direct child of Throwable and is unrelated to RuntimeException by inheritance, even though both are unchecked.

saying these in an interview costs you the question

  • Saying Error and Exception are siblings at the same level as RuntimeException (RuntimeException is under Exception, not a third top-level branch)
  • Claiming you cannot catch an Error (you technically can; you just shouldn't)
  • Calling all unchecked exceptions 'RuntimeExceptions' and forgetting Error is also unchecked
  • Thinking checked/unchecked is enforced by the JVM at runtime — it's a compile-time rule

context