skip to content

When would you choose @AfterThrowing over @ExceptionHandler / @ControllerAdvice, and vice versa?

level: middleimportance: nice to knowfreq 30%

answer

  1. AfterThrowing = observe, any layer
  2. ControllerAdvice = handle, web layer
  3. One logs, the other responds
  4. They can both fire on one failure
  5. Non-web handling → @Around

basics

~10 s

@AfterThrowing is a general AOP hook that observes exceptions from any advised bean method but can't change the outcome. @ExceptionHandler/@ControllerAdvice is Spring MVC's mechanism to catch controller exceptions and turn them into HTTP responses.

solid answer

~40 s

They solve different problems. @AfterThrowing is a low-level AOP advice: it fires whenever any pointcut-matched bean method throws, across all layers (services, repos, etc.), and is observe-only — it logs, counts, or alerts but cannot alter the response or swallow the exception. @ExceptionHandler (typically in a @ControllerAdvice) is Spring MVC/WebMVC machinery: it catches exceptions escaping controller handler methods and *produces* an HTTP response (status, body), i.e. it actually handles them for the web layer. Use @AfterThrowing for cross-cutting failure observability spanning non-web components; use @ControllerAdvice/@ExceptionHandler to convert exceptions into client-facing responses. They coexist: @AfterThrowing might log a service exception, which then propagates to a controller where @ExceptionHandler maps it to a 500/400. For non-observable handling in non-web code, @Around is the AOP option.

code

java · 15 lines
java
// Cross-cutting observability (any layer) — observe only
@Aspect @Component
class ErrorMetricsAspect {
    @AfterThrowing(pointcut = "execution(* com.example..*(..))", throwing = "ex")
    void count(Throwable ex) { /* metrics.increment(...) */ }
}

// Web-layer handling — turns exception into an HTTP response
@RestControllerAdvice
class ApiExceptionHandler {
    @ExceptionHandler(DataAccessException.class)
    ResponseEntity<String> onDb(DataAccessException ex) {
        return ResponseEntity.status(503).body("temporarily unavailable");
    }
}

go deeper

for a junior

Knows @ExceptionHandler handles controller exceptions.

for a middle

Distinguishes observe-only AOP vs response-producing web handling and that both can fire.

for a senior

Notes @Around for non-web handling and swallowing interactions.

for a principal

Designs layered failure strategy: observability (AOP) + web mapping + recovery.

### Two different mechanisms **`@AfterThrowing`** is part of **Spring AOP**. It advises *any* Spring bean method matched by a pointcut — service, repository, component — regardless of layer. It is **observe-only**: it runs when an exception is thrown but cannot change the exception, produce a response, or recover. **`@ExceptionHandler`**, usually declared inside a **`@ControllerAdvice`** (or `@RestControllerAdvice`) class, is part of **Spring MVC / WebFlux**. It catches exceptions thrown out of `@RequestMapping`/`@GetMapping` handler methods and **maps them to an HTTP response** (status code + serialized body). It is a genuine *handler* — it consumes the exception and dictates what the client receives. ### Comparison | Aspect | @AfterThrowing | @ExceptionHandler / @ControllerAdvice | |---|---|---| | Layer | Any advised bean | Web/controller layer only | | Framework | Spring AOP (proxy) | Spring MVC/WebFlux | | Can change outcome? | No (observe-only) | Yes (produces response) | | Typical use | Log/metrics/alert on failure | Translate exception → HTTP status/body | | Sees the exception | As it propagates | When it escapes the controller | ### How they combine A repository throws `DataAccessException`. An `@AfterThrowing` aspect logs it and increments an error metric, then the exception keeps propagating up through the service to the controller, where a `@RestControllerAdvice` `@ExceptionHandler(DataAccessException.class)` maps it to `503 Service Unavailable` with a JSON error body. Both fire; each does its job. ### Choosing - **Observability across all layers** (log/count/alert), no response change → `@AfterThrowing`. - **Turn an exception into a client response** → `@ExceptionHandler` in `@ControllerAdvice`. - **Non-web handling/recovery/translation** (retry, fallback, wrap) → `@Around`. ### Gotchas - `@ExceptionHandler` only catches exceptions reaching the DispatcherServlet from a handler; exceptions swallowed earlier (e.g., by an @Around) won't reach it. - `@AfterThrowing` won't fire for self-invoked or non-proxied calls (proxy limitation). - Don't put response-shaping logic in `@AfterThrowing` — it can't set the response.

  • Can @AfterThrowing set the HTTP status returned to the client?
    No. It's observe-only and layer-agnostic; shaping the HTTP response is the job of @ExceptionHandler/@ControllerAdvice (or an @Around that alters the flow).
  • If an @Around aspect on the service swallows an exception, will @ControllerAdvice still handle it?
    No. If the exception is swallowed before it reaches the controller/dispatcher, no exception escapes, so @ExceptionHandler never sees it.

saying these in an interview costs you the question

  • Claiming @AfterThrowing can produce or modify the HTTP response
  • Thinking @ExceptionHandler catches exceptions from any bean, not just controllers

context