Describe Kotlin's Throwable hierarchy. What are Throwable, Exception, RuntimeException, and Error, and which should you typically catch?
answer
- Throwable -> Exception + Error
- RuntimeException is unchecked branch
- Catch Exception, not Throwable/Error
- cause for chaining, addSuppressed for cleanup
- Rethrow CancellationException
basics
~20 sThrowable 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 sThe 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
Names Throwable/Exception/Error and knows to catch Exception.
Places RuntimeException and common stdlib exceptions correctly and explains cause.
Explains why catching Throwable is harmful and handles CancellationException in coroutines.
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