How does a chained exception appear in a printed stack trace, and how do you read it to find the root cause?
answer
- outer trace first, then 'Caused by:' per cause
- recurses until getCause() == null
- '... N more' collapses shared frames
- last 'Caused by:' = root cause; read bottom-up
- log the Throwable, not just getMessage()
basics
~20 sThe outer exception's stack trace prints first, then a line 'Caused by:' followed by the cause's trace, repeating down the chain. The deepest 'Caused by:' is usually the real root cause. Read from the bottom up.
solid answer
~40 sWhen you print a chained exception (printStackTrace or via a logger that logs the Throwable), Java prints the topmost exception with its full stack trace, then for each cause it prints a line beginning 'Caused by:' followed by that cause's trace, recursing all the way down. Frames shared between an exception and its cause are collapsed into '... N more' to keep output compact. To debug, read top-down for the symptom (what the caller saw), then jump to the *last* 'Caused by:' block — that deepest exception is typically the true root cause, and its first stack frame points at the line that originally failed. There can also be a separate 'Suppressed:' section for exceptions added via addSuppressed (e.g. from try-with-resources), which is distinct from the cause chain.
go deeper
Recognizes the 'Caused by:' lines and knows the root cause is typically the deepest one.
Reads a multi-level chain correctly, understands '... N more', and knows to log the Throwable not getMessage().
Distinguishes cause chains from suppressed exceptions and uses the trace structure to pinpoint the failing layer quickly.
Drives logging/observability standards so full cause chains reach monitoring and aren't truncated or flattened by log pipelines.
## What gets printed A `Throwable` knows how to render itself via `printStackTrace()` (and loggers call the same machinery when you pass them a Throwable). For a **chained** exception, the output has a recursive structure: ``` <ExceptionClass>: <message> at <frame> at <frame> ... Caused by: <CauseClass>: <cause message> at <frame> ... ... 7 more Caused by: <RootCauseClass>: <root message> at <frame> ... ``` - The **first block** is the exception that was actually thrown to the top (the outermost wrapper). - Each **`Caused by:`** block is `getCause()` of the block above it, printed recursively until `getCause()` returns `null`. ## The `... N more` line An exception and its cause usually share the *bottom* of the call stack (the same outer frames where one was caught and the other thrown). Printing those identical frames twice would be noise, so Java replaces the common tail with **`... N more`**, meaning "N frames identical to the enclosing trace's bottom." It is purely a compaction; no information is lost. ## How to read it for debugging 1. **Top block = the symptom.** It tells you what the outer layer reported (e.g. `ServiceException: could not process order`). Its frames show where the wrapping happened. 2. **Walk down the `Caused by:` chain.** Each step peels back one layer of translation. 3. **The last `Caused by:` is usually the root cause.** Its first `at` frame points to the exact line that originally failed (e.g. `at Jdbc.connect(Jdbc.java:88)` → `SQLException: connection refused`). That is where you start fixing. So although the *first thing you read* is the top, the *most diagnostic* part is at the **bottom** — hence the rule of thumb "read the bottom-most Caused by first." ## Caused by vs. Suppressed Don't confuse the **cause chain** with the **`Suppressed:`** section: - **`Caused by:`** = the exception that *triggered* this one (a causal relationship), set via constructor/initCause and read via `getCause()`. - **`Suppressed:`** = secondary exceptions that occurred *while handling* the primary one — most commonly an exception thrown by `close()` in a **try-with-resources** block, which the JVM attaches via `addSuppressed()` so it isn't lost. These print under a `Suppressed:` header and are read via `getSuppressed()`. Both show up in the same printed trace, but they answer different questions: *what caused this* vs. *what else went wrong during cleanup*. ## Practical tip When logging, always log the **Throwable object itself** (e.g. `log.error("failed", ex)`), never just `ex.getMessage()`. Only the Throwable form triggers the full `Caused by:` rendering; `getMessage()` gives you the top message alone and silently drops the entire cause chain.
- What does the '... N more' line in a stack trace mean?It means N stack frames at the bottom are identical to the bottom of the enclosing trace, so they're collapsed to avoid duplication. No information is lost; it's purely compaction.
- Why might a logged error show only the top message and no 'Caused by:'?Because the code logged ex.getMessage() (a String) instead of the Throwable. Only passing the Throwable to the logger renders the full cause chain. Always log the exception object itself.
saying these in an interview costs you the question
- Reading only the top exception and ignoring the deepest 'Caused by:' where the real root cause lives.
- Confusing 'Caused by:' (cause chain) with 'Suppressed:' (try-with-resources cleanup failures).
- Logging getMessage() instead of the Throwable, which drops the whole cause chain.
- Thinking '... N more' indicates lost or truncated information.