skip to content

Does @AfterThrowing swallow or handle the exception? How would you actually suppress or translate it?

level: seniorimportance: must knowfreq 60%

answer

  1. Observe-only, exception still escapes
  2. Advice throwing = masks the real cause
  3. @Around + proceed() to actually handle
  4. Repository + translator for persistence
  5. Around swallow can hide it from AfterThrowing

basics

~20 s

No. @AfterThrowing only observes the exception, then the original exception keeps propagating to the caller. To suppress, replace, or recover, use @Around: catch inside proceed() and either return a fallback or throw a different exception.

solid answer

~50 s

@AfterThrowing is observe-only. When the join point throws, Spring invokes the advice and then re-propagates the *same* exception to the caller — the advice cannot catch it, change it, or return a fallback. Even throwing a new exception from inside the advice is a bad idea: it replaces the original and masks the real failure (and mixes advice bugs with business failures). If you need to actually handle the exception — translate it (e.g. wrap a low-level exception in a domain exception), swallow it and return a default, or retry — you must use @Around, which gives you a ProceedingJoinPoint. You call proceed() inside a try/catch and decide the outcome: rethrow, throw a translated exception, or return a fallback value. So the rule of thumb: @AfterThrowing for failure observability (log/metrics/alert), @Around for failure control (translate/recover/retry).

code

java · 10 lines
java
// @AfterThrowing: can only observe
@AfterThrowing(pointcut = "repo()", throwing = "ex")
public void log(Throwable ex) { /* ex still propagates after this */ }

// @Around: can translate/suppress/recover
@Around("repo()")
public Object handle(ProceedingJoinPoint pjp) throws Throwable {
    try { return pjp.proceed(); }
    catch (SQLException ex) { throw new DataAccessResourceFailureException("db", ex); }
}

go deeper

for a junior

Knows it doesn't swallow the exception.

for a middle

Explains that @Around is needed to handle/translate.

for a senior

Discusses advice-throws-masks-cause and the observe-vs-control split.

for a principal

Weighs @Around translation vs framework exception-translation and advice ordering effects.

### The core semantic `@AfterThrowing` is **non-intercepting on the exception**: it runs *because* an exception was thrown, but it has **no control over the exception's fate**. After the advice returns, Spring lets the **original exception continue to propagate** to the caller. There is no mechanism in after-throwing advice to catch, suppress, or substitute the exception. Contrast the after-family: - `@AfterReturning` — observes the return value (can read, not replace). - `@AfterThrowing` — observes the exception (cannot suppress). - `@After` — finally-style; runs on both paths, observes neither. None of these can alter control flow. Only `@Around` can. ### What if the advice itself throws? If your `@AfterThrowing` method throws an exception, that **new exception replaces the original** on the way out — the caller sees the advice's exception instead of the real business failure. This is almost always a mistake: it hides the root cause and couples advice bugs to business flow. Keep after-throwing advice side-effect-only and defensive (never let logging/metrics code throw). ### How to actually handle: @Around ```java @Around("execution(* com.example.repo.*.*(..))") public Object translate(ProceedingJoinPoint pjp) throws Throwable { try { return pjp.proceed(); } catch (SQLException ex) { // translate low-level to domain exception throw new DataAccessResourceFailureException("DB call failed", ex); } } ``` With `@Around` you own the outcome: - **Swallow + fallback**: `catch (...) { return defaultValue; }` — the caller sees a normal return. - **Translate**: rethrow a different exception type (often wrapping the cause). - **Retry**: loop around `proceed()`. - **Rethrow**: `catch (...) { log(); throw ex; }` — behaves like @AfterThrowing but with full control. ### Exception translation the framework way For persistence specifically, Spring already does this via `@Repository` + `PersistenceExceptionTranslationPostProcessor`, which converts vendor exceptions into the `DataAccessException` hierarchy — a purpose-built alternative to hand-rolled @Around translation. ### Gotchas - Don't try to 'recover' in @AfterThrowing — you can't; the exception still escapes. - Order matters: if both @Around and @AfterThrowing apply and the @Around swallows the exception, the @AfterThrowing may not see it (no exception propagates from the inner chain). - Keep the advice free of throwing side effects. ### When to use which - **Observe only** (log, count, alert) → `@AfterThrowing`. - **Control the outcome** (translate, recover, retry, fallback) → `@Around`.

  • What happens if your @AfterThrowing method itself throws a new exception?
    That new exception replaces the original one seen by the caller, masking the real failure. Advice should never throw for this reason.
  • You need to return a cached fallback when a downstream call fails. Which advice, and why?
    @Around — only it can catch inside proceed() and substitute a return value; @AfterThrowing cannot change the outcome, the exception would still propagate.
  • How does Spring translate JDBC/JPA exceptions without you writing @Around?
    Via @Repository and PersistenceExceptionTranslationPostProcessor, which map vendor exceptions to the DataAccessException hierarchy.

saying these in an interview costs you the question

  • Claiming @AfterThrowing catches/handles the exception so the caller sees success
  • Recommending throwing a translated exception from @AfterThrowing to convert error types
  • Thinking you can return a fallback value from @AfterThrowing

context