skip to content

Describe Kotlin's Throwable hierarchy. What are Throwable, Exception, RuntimeException, and Error, and which should you typically catch?

level: middleimportance: should knowfreq 58%

answer

  1. Throwable -> Exception + Error
  2. RuntimeException is unchecked branch
  3. Catch Exception, not Throwable/Error
  4. cause for chaining, addSuppressed for cleanup
  5. Rethrow CancellationException

basics

~20 s

Throwable is the root of everything you can throw or catch. Exception and Error are its two children. Exception (and its child RuntimeException) means recoverable problems; Error means serious JVM failures you normally let crash. Catch Exception, not Throwable.

solid answer

~40 s

The root is `kotlin.Throwable`, which has a `message: String?`, a `cause: Throwable?`, and supports suppressed exceptions. Two direct subtypes: `Exception` (application-level problems) and `Error` (severe conditions like `OutOfMemoryError`, `StackOverflowError`). Under `Exception` sits `RuntimeException`, the parent of the everyday stdlib failures such as `IllegalArgumentException`, `IllegalStateException`, `IndexOutOfBoundsException`, `NullPointerException`, `NumberFormatException`, `ClassCastException`, and `ConcurrentModificationException`. In Kotlin all of these are unchecked. You typically catch `Exception` (recoverable) and avoid catching `Throwable`/`Error`, because `Error` indicates the VM is in a bad state. In coroutines you must also avoid swallowing `CancellationException`, which is a normal `RuntimeException` signalling cancellation — catching a broad `Exception` and not rethrowing it breaks structured concurrency. Use `cause` to chain and `addSuppressed` for try-with-resources cleanup failures.

go deeper

for a junior

Names Throwable/Exception/Error and knows to catch Exception.

for a middle

Places RuntimeException and common stdlib exceptions correctly and explains cause.

for a senior

Explains why catching Throwable is harmful and handles CancellationException in coroutines.

for a principal

Sets team policy on exception granularity, wrapping/chaining, and suppressed-exception logging across modules.

## The hierarchy ``` Throwable ├── Error // serious, usually unrecoverable (JVM-level) │ ├── OutOfMemoryError │ └── StackOverflowError └── Exception // application-level └── RuntimeException ├── IllegalArgumentException │ └── NumberFormatException ├── IllegalStateException ├── IndexOutOfBoundsException ├── NullPointerException ├── ClassCastException ├── ConcurrentModificationException └── kotlinx.coroutines.CancellationException ``` `kotlin.Throwable` is the root; `kotlin.Exception` and `kotlin.Error` are typealiases/mappings onto the JVM's `java.lang.Exception`/`java.lang.Error`. ## What lives on Throwable - **`message: String?`** — human-readable description. - **`cause: Throwable?`** — the underlying exception when you wrap one in another (chaining). - **`stackTrace`** — array of stack frames. - **Suppressed exceptions** — `addSuppressed(...)` / `suppressedExceptions`, used when a cleanup (e.g. `close()`) fails while another exception is propagating. ## Error vs Exception - **`Error`** signals conditions a normal program should **not** try to recover from: `OutOfMemoryError`, `StackOverflowError`, `NoClassDefFoundError`. The VM may be unstable, so catching them is rarely useful. - **`Exception`** signals problems an application can reasonably handle. - **`RuntimeException`** is the unchecked branch of `Exception` and the parent of nearly all stdlib-thrown failures. ## What to catch - Prefer catching a **specific** type (`NumberFormatException`) or `Exception` for broad recovery. - **Avoid `catch (t: Throwable)`** and `catch (e: Error)` — you'd intercept VM errors you can't sensibly handle. ## Coroutine caveat: CancellationException `CancellationException` extends `IllegalStateException` (a `RuntimeException`). It is thrown to *cancel* a coroutine normally. A blanket `catch (e: Exception)` that doesn't rethrow it will **break structured concurrency**, because the coroutine machinery relies on that exception propagating. Always rethrow it: ```kotlin try { work() } catch (e: CancellationException) { throw e // never swallow cancellation } catch (e: Exception) { handle(e) } ``` ## Key terms - **`cause`**: wrapped lower-level exception (chaining). - **Suppressed exceptions**: secondary failures recorded during cleanup. - **Structured concurrency**: coroutine model where cancellation propagates predictably.

  • Why is catching Throwable dangerous?
    It also catches Errors like OutOfMemoryError and, in coroutines, CancellationException — conditions you usually shouldn't silently handle.
  • Where does NumberFormatException sit in the hierarchy?
    It extends IllegalArgumentException, which extends RuntimeException -> Exception -> Throwable.

Exception is a flat tire you can change; Error is the engine block cracking — pulling over to fix it yourself rarely helps.

saying these in an interview costs you the question

  • Catching Throwable to 'be safe'
  • Treating Error as recoverable
  • Swallowing CancellationException in coroutine code
  • Not knowing RuntimeException is the unchecked branch
  • Confusing message and cause

context