How do the returning and throwing attributes combine with args() binding to expose full context to advice?
answer
- returning binds return value; throwing binds exception
- bind by name — argNames if unresolved
- declared type filters when advice fires
- AfterThrowing observes, does not swallow
- args(input)+returning(output) = full context
basics
~20 s@AfterReturning has a returning attribute that binds the method's return value to a named advice parameter; @AfterThrowing has a throwing attribute that binds the thrown exception. Combined with args() you can access the input argument, the result, or the exception together in one advice.
solid answer
~40 s@AfterReturning(returning="result") binds the method's return value into an advice parameter named result; the advice only runs if the parameter type is assignable to the actual return type (so it also acts as a filter). @AfterThrowing(throwing="ex") binds the propagated exception the same way, and only runs when the thrown type matches. Pair these with args(input) to see the argument and the outcome together: you can log "save(account) returned id / threw DataAccessException". All three (args, returning, throwing) bind by name, so you list them in argNames if needed. Type-based filtering is a key feature: @AfterReturning(returning="list") with List list only fires when the return value is a List.
code
java · 19 lines@Aspect
@Component
public class OutcomeAspect {
@AfterReturning(
pointcut = "execution(* com.app.AccountService.save(..)) && args(account)",
returning = "saved")
public void logSuccess(Account account, Account saved) {
log.info("saved {} -> id {}", account.getEmail(), saved.getId());
}
// Only fires for DataAccessException; the exception still propagates.
@AfterThrowing(
pointcut = "execution(* com.app.AccountService.save(..)) && args(account)",
throwing = "ex")
public void logFailure(Account account, DataAccessException ex) {
log.error("save failed for {}: {}", account.getEmail(), ex.getMessage());
}
}go deeper
Know returning binds the result and throwing binds the exception into advice parameters.
Explain type-based filtering, by-name binding, and combining with args() for input+outcome context.
Discuss void/null edge cases, that AfterThrowing observes only, and when @Around is required instead.
Weigh observational advice vs @Around for cross-cutting concerns; design type-filtered, non-invasive auditing/metrics that don't alter control flow.
**The two attributes.** - `@AfterReturning(pointcut=..., returning="retVal")` runs after a matched method returns *normally*. Spring binds the return value to the advice parameter named `retVal`. - `@AfterThrowing(pointcut=..., throwing="ex")` runs after a matched method exits by *throwing*. Spring binds the thrown exception to the parameter named `ex`. **Binding is by name.** The string in `returning`/`throwing` must equal an advice parameter name — just like args(). Spring resolves names via the same ParameterNameDiscoverer machinery (-parameters, debug info, or argNames). Because everything binds by name, the *order* of advice parameters is free; only names matter. **Type acts as a filter (important).** The declared type of the bound parameter narrows when the advice fires: - `@AfterReturning(returning="acc")` with `Account acc` only runs when the return value is assignable to Account. A method returning String is skipped. - `@AfterThrowing(throwing="ex")` with `DataAccessException ex` only fires for that exception hierarchy; other exceptions pass through un-adviced. Use `Object` to catch everything, or a specific type to filter. Note: for @AfterThrowing, the exception still propagates to the caller — this advice observes, it does not swallow. To suppress or translate an exception you need @Around with try/catch. **Combining with args().** This is where full context comes together: ```java @AfterReturning( pointcut = "execution(* com.app.AccountService.save(..)) && args(account)", returning = "saved") public void onSaved(Account account, Account saved) { ... } ``` Now you have both the *input* argument (`account`) and the *output* (`saved`). Similarly you can combine args() with @AfterThrowing to log which input caused which failure. **Edge cases and gotchas.** - **void methods**: @AfterReturning still runs; the bound return value is null. If you declare a non-Object, non-null-compatible primitive parameter you can get a mismatch — prefer Object or a reference type. - **null return**: a null return value does not match a specific returning type (similar to args null semantics), so the advice may be skipped if you typed it narrowly. - **@AfterReturning cannot modify the return value** — it only observes. Use @Around and return a new value to change it. - **@After (finally)** runs regardless of outcome but cannot bind returning/throwing. - **@Around** can access everything via ProceedingJoinPoint.getArgs() and its return of proceed(), and is the only advice that can both read and alter args/return/exception. **When to use.** args + returning/throwing gives declarative, type-filtered observation of a method's inputs and outcome without touching the method — ideal for auditing, metrics, structured logging, and post-commit event publication. Reach for @Around only when you must change arguments, alter the result, retry, or swallow/translate exceptions.
- Can @AfterReturning change the value the caller receives?No. @AfterReturning only observes the return value. To modify what the caller gets, use @Around and return a different value from (or instead of) proceed().
- You declared throwing="ex" with type IllegalArgumentException but a NullPointerException is thrown. Does the advice run?No. The bound type filters: only exceptions assignable to IllegalArgumentException trigger it. The NPE propagates without this advice firing. Use Throwable to catch all.
saying these in an interview costs you the question
- Claiming @AfterThrowing suppresses/handles the exception (it doesn't; it only observes).
- Thinking @AfterReturning can mutate the return value.
- Believing returning/throwing bind by position rather than by name.