skip to content

When multiple aspects advise the same join point, how is @Before ordering determined, and what happens if a @Before throws?

level: principalimportance: should knowfreq 30%

answer

  1. @Order / Ordered; lower value = first for @Before
  2. Onion: before outer→inner, after inner→outer
  3. Throw short-circuits: no inner before, no target
  4. Entered outer layers still get @After/@AfterThrowing
  5. Security/validation @Before = highest precedence

basics

~20 s

Order is set by @Order / Ordered on the aspects: for before advice, the lowest order value (highest priority) runs first. If a @Before throws, later before advices and the target don't run; the exception propagates like an unwound stack.

solid answer

~50 s

When several aspects match one join point, Spring orders them by precedence — @Order annotation or the Ordered interface on each aspect; lower value = higher precedence. For @Before advice, the highest-precedence aspect runs first (its before advice is 'outermost'). Within a single aspect, the ordering of multiple advices is not guaranteed by declaration order, so put them in separate ordered aspects if order matters. If a @Before throws, the chain short-circuits: no later before advices run, the target method never executes, and the exception propagates outward — after/around advice on higher-precedence aspects can still observe it on the way out (their @After/@AfterThrowing run as the stack unwinds), because advice nests like an onion. This is why validation/security @Before is given high precedence: it fires first and can abort before cheaper concerns like logging even start on the target.

code

java · 21 lines
java
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;

@Aspect @Component
@Order(Ordered.HIGHEST_PRECEDENCE)          // gate first
public class SecurityAspect {
    @Before("execution(* com.app.api..*(..))")
    public void authorize(JoinPoint jp) {
        if (!currentUserAllowed())
            throw new AccessDeniedException("forbidden"); // aborts: target + inner advices skipped
    }
}

@Aspect @Component
@Order(100)                                 // runs after security's @Before
public class EntryLogAspect {
    @Before("execution(* com.app.api..*(..))")
    public void log(JoinPoint jp) {
        log.info("enter {}", jp.getSignature().toShortString());
    }
}

go deeper

for a junior

Know @Order controls sequence and a throw stops the call.

for a middle

Explain lower order = first for @Before and that a throw skips the target.

for a senior

Describe the onion nesting and which after-advices still run on a throw.

for a principal

Design precedence conventions (security/validation first), reason about interaction with the transaction advisor's order, and avoid intra-aspect order dependence.

## The advice chain is an onion At a single join point, all matching advices form a nested interceptor chain. Think of each aspect as a layer wrapping the target. Higher-precedence aspects are the **outer** layers. For a call: - **Before** advice runs **outer → inner** (highest precedence first). - The **target** runs in the center. - **After** advice runs **inner → outer** (highest precedence last) — the reverse. ## Determining precedence Spring orders aspects (not individual advice methods across aspects) by: - **`@Order(n)`** annotation (`org.springframework.core.annotation.Order`) on the aspect class, or - implementing **`org.springframework.core.Ordered`** and returning `getOrder()`. **Lower value = higher precedence = runs first for before advice.** `Ordered.HIGHEST_PRECEDENCE` is `Integer.MIN_VALUE`, `LOWEST_PRECEDENCE` is `Integer.MAX_VALUE`. Aspects with no explicit order are effectively unordered relative to each other — do **not** rely on class name or declaration order. ### Within one aspect If a single aspect declares multiple advices matching the same join point, their relative invocation order is **not defined by source order**. Historically Spring didn't guarantee it; even where it's influenced by declaration order in newer AspectJ, the robust design is: **if ordering matters, split the advices into separate aspects and order those.** ```java @Aspect @Component @Order(0) // runs FIRST among before advices class SecurityAspect { @Before("...") void check() { /* authz */ } } @Aspect @Component @Order(10) // runs after SecurityAspect's before class LoggingAspect { @Before("...") void log() { /* entry log */ } } ``` ## What happens when a @Before throws Throwing is `@Before`'s only way to abort. When it throws: 1. **No later before advices** (inner layers) run. 2. **The target method never executes.** 3. The exception **propagates outward** through the already-entered outer layers. Because of the onion nesting, **outer** aspects that had already run their before advice *and* have after/around handling can still react as the stack unwinds: - An outer aspect's **`@After`** (finally-style) advice **still runs**. - Its **`@AfterThrowing`** advice runs, observing the exception. - Its **`@AfterReturning`** does **not** (there was no successful return). - An outer **`@Around`** that wrapped the call sees the exception thrown from its `proceed()` and can catch/translate it. Inner aspects whose before advice never ran do **not** get after advice — they were never entered. ### Concrete trace Given Security(@Order 0) and Logging(@Order 10), both with `@Before` and `@After`, if Security's `@Before` throws: - Security `@Before` runs → throws. - Logging `@Before` never runs (inner, not reached). - Target never runs. - Security `@After`/`@AfterThrowing` — Security's before was the one that threw; whether its own `@After` runs depends on it being a separate advice at the same/enclosing layer. In practice, the exception simply propagates to the caller; only advices at *enclosing* layers relative to the throw point observe it. The safe mental model: **the throw unwinds the partially-built onion**; layers entered before the throw get their finally/after-throwing hooks, layers not yet entered get nothing. ## Design implications (why a principal cares) - **Put security and validation `@Before` at highest precedence** (`@Order(Ordered.HIGHEST_PRECEDENCE)` or a low number) so they gate the call *before* logging/metrics/transaction advice does work that would be wasted or misleading. - **Interaction with `@Transactional`:** the transaction aspect has its own default order (`Ordered.LOWEST_PRECEDENCE` by default). If a `@Before` must run inside or outside the transaction (e.g., validation that should not open a tx vs. auditing that must be transactional), set explicit orders relative to it, or configure the transaction advisor's order. - **Fail-fast placement reduces cost and side effects** — an early throw avoids opening transactions, acquiring locks, or emitting misleading entry logs. - **Avoid order-dependent logic inside one aspect;** model each ordered concern as its own aspect for deterministic, testable sequencing. - **Document orders centrally** — implicit ordering across many aspects is a maintenance hazard; a team convention (e.g., security=0, validation=100, audit=200, logging=300) keeps it legible.

  • Two @Before advices are declared in the same @Aspect class. Can you rely on source order for their execution?
    No — relative ordering of multiple advices within one aspect is not guaranteed. If order matters, split them into separate aspects and set @Order on each.
  • A high-precedence @Before throws. Does a lower-precedence aspect's @After advice run?
    No — the lower-precedence (inner) aspect was never entered, so its before/after advice don't run. Only layers already entered (higher precedence) observe the unwinding via @After/@AfterThrowing.
  • How do you make a validation @Before run outside a @Transactional boundary?
    Give the validation aspect higher precedence (lower @Order) than the transaction advisor, whose default order is Ordered.LOWEST_PRECEDENCE — so validation's before advice runs before the transaction is opened.

saying these in an interview costs you the question

  • Believing higher @Order value runs first (it's the opposite for before advice)
  • Relying on declaration order for multiple advices inside one aspect
  • Thinking a throwing @Before still lets the target or inner advices run
  • Assuming inner aspects get after-advice even when their before never ran

context