What is the difference between rethrowing an exception, wrapping it in a new one, and how does the cause chain preserve propagation context?
answer
- Rethrow = same object, original trace kept
- Wrap = new type + original as cause
- Cause chain shows 'Caused by:'
- Translate at layer boundaries
- Never wrap without the cause; never swallow
basics
~20 sRethrowing lets the same exception keep going up. Wrapping creates a new exception that carries the original as its cause, so you change the type or add context without losing the original. The cause chain keeps the full history visible in the stack trace.
solid answer
~50 sWhen you catch an exception you have choices. Rethrow (`throw e;`) sends the same object onward, preserving its original stack trace, so propagation continues from where it was first thrown. Wrapping (`throw new ServiceException("...", e)`) creates a new exception of a more meaningful type and stores the caught one as its cause via the cause chain; the printed trace shows the new exception then 'Caused by:' with the original, so no context is lost. Use wrapping at architectural boundaries to translate low-level exceptions (IOException, SQLException) into domain exceptions without leaking implementation. Avoid swallowing (catch and ignore) or rethrowing a brand-new exception without passing the cause, which destroys the original stack trace and makes debugging hard. A subtlety: re-throwing the same object does not reset its stack trace to the catch point; the original throw location is retained, which is what you want for accurate propagation history.
code
java · 14 lines// Wrap (exception translation) at a layer boundary:
try {
return jdbc.queryForUser(id);
} catch (SQLException e) {
throw new RepositoryException("load user " + id + " failed", e); // e = cause
}
// Observe then rethrow the same object (original trace preserved):
try {
process();
} catch (IOException e) {
metrics.increment("io_error");
throw e;
}go deeper
Knows you can catch and rethrow, and that wrapping keeps the original as a cause.
Explains the cause chain and the 'Caused by:' trace, and that wrapping changes the type while preserving the original.
Applies exception translation at layer boundaries, knows rethrow keeps the original trace, and avoids wrapping without a cause.
Defines an exception-handling strategy across layers, standardizes domain exception types and logging-vs-propagation boundaries, and reasons about diagnosability in distributed systems.
## Vocabulary first - **Catch**: intercept a propagating exception in a `catch` block. - **Stack trace**: the snapshot of frames captured **when the exception object is constructed**, recording where it was thrown. - **Cause**: a `Throwable` reference one exception holds pointing to the exception that triggered it (`getCause()`); chains form a **cause chain**. ## The three things you can do after catching ### 1. Rethrow the same exception ```java try { risky(); } catch (IOException e) { log(e); throw e; // same object continues up the stack } ``` The **same** object resumes propagation. Crucially, its stack trace still points to the **original** throw site (constructing the object is what captured the trace; rethrowing doesn't recapture). The exception simply continues unwinding from this frame onward. Good when you only wanted to observe/log before letting it pass. ### 2. Wrap it (exception translation / chaining) ```java try { jdbc.query(...); } catch (SQLException e) { throw new RepositoryException("load user failed", e); // e becomes the cause } ``` A **new** exception (often a more meaningful, higher-level type) is thrown, and the caught one is stored as its **cause**. The new exception's trace shows where *it* was created; below it the trace prints: ``` Caused by: java.sql.SQLException: connection reset at ... ``` Nothing is lost — you get both the high-level context and the low-level root. This is **exception translation**, ideal at layer boundaries (data layer → service layer) so callers depend on your domain exceptions, not on JDBC. ### 3. Swallow (anti-pattern) ```java catch (Exception e) { } // DON'T: propagation stops, context vanishes ``` The exception is silenced; propagation halts and the problem disappears. Almost always a bug. ## Why the cause chain matters for propagation Propagation carries an exception up the stack, but at a boundary you often must change its *type*. Without a cause, the new exception's trace would start at the boundary and the real origin would be gone. By attaching the cause, the propagation **history** survives the type change: the chain reads top-down from the most recent (boundary) exception to the deepest root cause. Debuggers and `printStackTrace()` walk this chain automatically. ## Two real subtleties - **Rethrow does not reset the trace.** Many assume `throw e;` makes the trace point to the catch line. It doesn't — the trace was fixed at construction. (`fillInStackTrace()` would re-capture, but you rarely call it.) - **Wrap with the cause, never without.** `throw new ServiceException("failed")` (omitting `e`) is exactly as destructive as swallowing for diagnostics — the root cause is lost. Always pass the cause. ## Deriving the answer Rethrow = same object continues with its original trace; wrap = new typed exception carrying the original as cause so the full chain (and history) survives a type change; swallowing or wrapping-without-cause destroys context. Choose wrapping at boundaries, rethrow for observe-then-continue.
- Does rethrowing with 'throw e;' change the exception's stack trace to point at the catch block?No. The stack trace is captured when the exception is constructed, not when it's rethrown. The original throw location is preserved, which is the desired behavior for accurate propagation history.
- When should you translate (wrap) an exception versus letting it propagate as-is?Wrap at architectural boundaries to convert low-level/implementation exceptions into domain-meaningful types so callers don't depend on implementation details. Let it propagate as-is when the same layer or callers naturally handle that type.
saying these in an interview costs you the question
- Wrapping without passing the cause, destroying the original stack trace.
- Believing 'throw e;' resets the trace to the catch line.
- Catching and swallowing exceptions to 'clean up' logs.
- Translating every exception unnecessarily, hiding useful low-level types within a layer.