How does @After differ from @AfterReturning and @AfterThrowing?
answer
- Returning = success + value
- Throwing = exception + that exception
- After = both paths, binds nothing
- Need value AND finally -> @Around
- Typed param narrows when it fires
basics
~20 s@AfterReturning runs only on a normal return and can bind the return value. @AfterThrowing runs only when an exception is thrown and can bind that exception. @After runs in both cases (finally) but can bind neither the value nor the exception.
solid answer
~50 sAll three are 'after' advice, differing by which path they fire on and what they can bind. @AfterReturning fires only when the method returns normally and can capture the result via its returning attribute: @AfterReturning(pointcut = "...", returning = "result"). @AfterThrowing fires only on the exception path and can capture the exception via throwing = "ex"; it still lets the exception propagate. @After fires on both paths — like a finally block — and has no returning or throwing attribute, so it sees neither the value nor the exception, only the JoinPoint. Choose @AfterReturning when you need the result (e.g. audit the response), @AfterThrowing when you need the exception (e.g. error metrics), and @After when cleanup must happen unconditionally and needs neither. If a single piece of cleanup needs both the result and guaranteed execution, use @Around with try/finally instead.
code
java · 13 lines@Aspect
@Component
public class AuditAspect {
@AfterReturning(pointcut = "execution(* com.acme.OrderService.place(..))", returning = "order")
public void onSuccess(Order order) { metrics.recordPlaced(order.getId()); }
@AfterThrowing(pointcut = "execution(* com.acme.OrderService.place(..))", throwing = "ex")
public void onError(Exception ex) { metrics.recordFailure(ex.getClass()); }
@After("execution(* com.acme.OrderService.place(..))")
public void always(JoinPoint jp) { RequestContext.clear(); } // cleanup either way
}go deeper
Know the three fire conditions: returning=success, throwing=exception, after=both.
Must know the binding differences (returning/throwing attributes) and that @After binds neither.
Explain typed-parameter filtering and when to escalate to @Around for value+finally.
Discuss designing an aspect that combines outcome-specific advice with unconditional cleanup, and the ordering guarantees that make it correct.
### The three 'after' advice types Spring AOP has three advice types that run *after* the join point, and they are complementary: | Advice | Runs on normal return | Runs on exception | Can bind return value | Can bind exception | |---|---|---|---|---| | `@AfterReturning` | yes | no | yes (`returning`) | no | | `@AfterThrowing` | no | yes | no | yes (`throwing`) | | `@After` | yes | yes | no | no | ### @AfterReturning Runs **only** if the method completes without throwing. Bind the returned object with the `returning` attribute; the parameter name must match: ``` @AfterReturning(pointcut = "execution(* com.acme..*(..))", returning = "result") public void logResult(JoinPoint jp, Object result) { ... } ``` If you declare a specific type (e.g. `Order result`), the advice only runs when the returned value is assignable to that type — this is an implicit filter. `void` methods return `null` here. ### @AfterThrowing Runs **only** if the method throws. Bind the exception with `throwing`: ``` @AfterThrowing(pointcut = "execution(* com.acme..*(..))", throwing = "ex") public void logError(JoinPoint jp, RuntimeException ex) { ... } ``` If you type the parameter (e.g. `DataAccessException ex`), the advice fires **only** for exceptions of that type — others pass through untouched. Crucially, `@AfterThrowing` does **not** handle/suppress the exception; it observes it, then the exception continues to propagate. To actually stop or translate an exception you need `@Around` (catch inside `proceed()`). ### @After (finally) Runs on **both** paths. It has neither `returning` nor `throwing`, so it cannot observe the outcome — only the `JoinPoint`. It is the right tool when the action is unconditional cleanup: releasing a lock, clearing `ThreadLocal`/MDC, closing a resource opened by a `@Before` advice. ### Why not just use @After for everything? Because `@After` is blind to the outcome. If you need to record the response body, you need `@AfterReturning`. If you need the exception's message or type for metrics, you need `@AfterThrowing`. And if you need **both** the value/exception **and** guaranteed cleanup in one place, none of these suffice individually — use `@Around`: ``` @Around("...") public Object around(ProceedingJoinPoint pjp) throws Throwable { try { return pjp.proceed(); } finally { /* guaranteed cleanup with full control */ } } ``` ### Common combination A frequent pattern is `@AfterReturning` + `@AfterThrowing` for outcome-specific logging **plus** `@After` for the unconditional cleanup. That works, but be aware their relative ordering within one aspect matters (see the ordering question) — since Spring 5.2.7 `@After` runs after `@AfterReturning`/`@AfterThrowing`. ### Gotchas - Typing the bound parameter narrows *when* `@AfterReturning`/`@AfterThrowing` fire — a deliberate filter, easy to misread as 'it didn't run'. - `@After` firing on the exception path can mask assumptions: it is not a `catch`, and the exception is still thrown. - All three see the same proxy/self-invocation limitations as any Spring AOP advice.
- You typed the @AfterThrowing parameter as IllegalStateException but the advice never fires though errors happen. Why?Typing the throwing parameter filters by exception type — the advice only runs for IllegalStateException (or subtypes). Other exception types are not matched, so it never fires for them. Widen the type (e.g. Throwable/Exception) or remove the binding.
- If you need both the return value and guaranteed cleanup, which advice do you use?@Around: call proceed(), keep the returned value, and put the guaranteed cleanup in a finally block. No single after-advice can bind the value and also run on the exception path.
saying these in an interview costs you the question
- Claiming @After can access the return value or exception
- Thinking @AfterThrowing catches/suppresses the exception
- Assuming @AfterReturning still runs when the method throws