skip to content

In what order do @Before, @After, @AfterReturning, @AfterThrowing, and @Around fire around a single method, and how does @AfterReturning fit on the success vs failure paths?

level: seniorimportance: should knowfreq 40%

answer

  1. Around-pre → Before → body → AfterReturning/AfterThrowing → After → Around-post
  2. @After always; AfterReturning success-only
  3. AfterReturning & AfterThrowing mutually exclusive
  4. Lower @Order = outer/higher precedence
  5. 5.2.7 aligned same-aspect after ordering

basics

~10 s

Around-before → Before → method → (on success) AfterReturning, (on failure) AfterThrowing → After (always) → Around-after. @AfterReturning fires only on the success path, before @After, and never on the exception path.

solid answer

~40 s

For one aspect around one method the sequence is: @Around code before proceed(), then @Before, then the target executes. On normal return: @AfterReturning runs, then @After (finally-style), then @Around resumes after proceed(). On a thrown exception: @AfterThrowing runs, then @After, and the exception propagates out through @Around (which sees it at proceed()). So @After always runs; @AfterReturning and @AfterThrowing are mutually exclusive per invocation. Across multiple aspects, ordering follows @Order / Ordered — lower value = higher priority = outermost on the 'before' side and, symmetrically, outermost/last on the 'after' side. Note the historical Spring 5.2.7+ fix that made same-aspect after-advice ordering consistent (afterReturning/afterThrowing then after, matching AspectJ) — worth knowing if asked about legacy behavior.

code

java · 27 lines
java
@Aspect
@Component
public class LifecycleAspect {
    private static final Logger log = LoggerFactory.getLogger(LifecycleAspect.class);

    @Pointcut("execution(* com.app.PaymentService.charge(..))")
    void charge() {}

    @Before("charge()")               void before()      { log.info("1 before"); }
    @AfterReturning("charge()")       void afterReturn() { log.info("3a after-returning (success only)"); }
    @AfterThrowing("charge()")        void afterThrow()  { log.info("3b after-throwing (failure only)"); }
    @After("charge()")                void after()       { log.info("4 after (always)"); }

    @Around("charge()")
    Object around(ProceedingJoinPoint pjp) throws Throwable {
        log.info("0 around-before");
        try {
            Object r = pjp.proceed();
            log.info("5 around-after (success)");
            return r;
        } catch (Throwable t) {
            log.info("5 around-catch (failure)");
            throw t;
        }
    }
}
// Success log order: 0,1,(body),3a,4,5

go deeper

for a junior

Knows AfterReturning is success-only and After always runs.

for a middle

Can lay out the full single-aspect ordering.

for a senior

Explains @Order precedence and success vs failure branching precisely.

for a principal

Recalls the 5.2.7 same-aspect fix and designs aspect ordering deliberately.

## Single aspect, single method — the canonical order Think of advice as nested shells around the target: 1. `@Around` — code **before** `proceed()` 2. `@Before` 3. **target method body** 4a. success → `@AfterReturning` 4b. exception → `@AfterThrowing` 5. `@After` (runs on **both** paths — like `finally`) 6. `@Around` — code **after** `proceed()` (only reached on success; on failure the exception unwinds through it, where it can catch/re-throw) So on the **success path**: Around-pre → Before → method → AfterReturning → After → Around-post. On the **failure path**: Around-pre → Before → method(throws) → AfterThrowing → After → exception propagates (Around-post is skipped unless @Around catches it). ## Where @AfterReturning sits `@AfterReturning` is strictly a **success-path, pre-@After** hook. It runs after the method produced a value but before the finally-style `@After`. It is never invoked when the method throws. ## Multiple aspects — @Order / Ordered When several aspects advise the same join point, the framework sorts them by their `org.springframework.core.Ordered` value (or `@Order` annotation): **lower number = higher precedence**. The highest-precedence aspect is **outermost** — its 'before'-style advice runs first and its 'after'-style advice runs last, wrapping the others. Two aspects with the same order have **undefined** relative ordering, which is a real-world flakiness source; always set explicit orders when interactions matter (e.g. security before transaction, transaction before caching). ## Same-aspect ordering history (Spring 5.2.7) Before Spring Framework 5.2.7, advice methods **declared in the same @Aspect class** could run in an order that didn't match AspectJ's precedence. Since 5.2.7 the behavior was aligned so that on the way out, `@AfterReturning`/`@AfterThrowing` run before `@After`, consistent with AspectJ. If an interviewer probes legacy quirks or you support old Spring versions, mention that a single class mixing multiple advice types historically had surprising ordering; splitting advice across ordered aspects avoided it. ## Practical implications - Don't put cleanup that must always run in `@AfterReturning` — it's skipped on exceptions; use `@After`. - Success-only side effects (audit 'completed', metrics increment, cache put) belong in `@AfterReturning` so they don't fire on failure. - If success handling must be able to see and react to the returned value AND you also need guaranteed cleanup, combine `@AfterReturning` (success reaction) with `@After` (cleanup). ## Self-invocation reminder All of this only applies to calls routed through the proxy. Internal `this.method()` calls bypass advice entirely.

  • Two aspects both advise the same method and have no @Order. Is the ordering guaranteed?
    No. With equal precedence the relative order is undefined and can vary. Assign explicit @Order/Ordered values when the interaction matters (e.g. security must wrap transactions).
  • You need cleanup that runs even when the method throws — which advice, and why not @AfterReturning?
    Use @After (finally-style), which runs on both success and exception. @AfterReturning is skipped on any thrown exception, so cleanup placed there would leak on failures.

saying these in an interview costs you the question

  • Saying @AfterReturning runs after @After
  • Claiming @AfterReturning and @AfterThrowing can both fire in one invocation
  • Thinking higher @Order number means higher precedence
  • Assuming same-order aspects have deterministic ordering

context