Explain the semantics of proceed() in the interceptor chain: what it invokes, multiple/zero calls, and the return-value and exception contract.
answer
- proceed() = next link, not direct target
- zero = short-circuit, many = retry
- last link runs the real method
- returns Object, throws Throwable
- @Order controls wrapping
basics
~20 sproceed() advances the AOP interceptor chain to the next advice, and eventually the target method. Not calling it skips the target; calling it multiple times re-invokes the chain (retry). It returns the downstream result and propagates any Throwable, so you must return its value.
solid answer
~50 sWhen multiple aspects advise the same method, Spring composes them into an ordered interceptor chain. Inside an @Around, proceed() means 'invoke the next link' — the next @Around advice, or, at the end, the real target method. So proceed() is not a direct call to the target; it's a step down the chain. This gives @Around full control: skip proceed() to short-circuit (caching, auth) so nothing downstream runs; call it once for normal flow; call it repeatedly to retry or benchmark, each call re-entering the remaining chain. proceed() returns the Object that the downstream chain/target produced and declares throws Throwable, so exceptions from the target propagate through unless you catch them. You must return proceed()'s value or the caller gets the wrong result. Advice order is controlled by @Order / Ordered, and nested @Around advices wrap each other accordingly.
code
java · 25 linesimport org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Aspect
@Component
@Order(10) // lower = outermost
public class RetryAspect {
@Around("@annotation(com.katajob.Retryable)")
public Object retry(ProceedingJoinPoint pjp) throws Throwable {
int attempts = 3;
Throwable last = null;
for (int i = 1; i <= attempts; i++) {
try {
return pjp.proceed(); // re-enters the remaining chain each attempt
} catch (Throwable t) { // proceed() declares throws Throwable
last = t;
}
}
throw last; // propagate after exhausting retries
}
}go deeper
Know proceed() runs the method and you must return its result.
Explain zero-call short-circuit and single-call normal flow with exception propagation.
Describe the chain (next link vs target), retry via multi-proceed, and idempotency caveats.
Reason about aspect ordering, chain composition, exception/return contracts, and side-effect safety when designing cross-cutting infrastructure.
## Interceptor chain model When one or more `@Around` advices (and other advice kinds) apply to a method, Spring builds an ordered **interceptor chain** (implemented via `MethodInterceptor`s wrapping a `MethodInvocation`). Each `@Around` advice sits as one link. - Calling `ProceedingJoinPoint.proceed()` invokes `MethodInvocation.proceed()` internally — i.e. it advances to the **next** interceptor in the chain. - Only the **last** link actually invokes the real target method. So conceptually: `outerAround -> proceed() -> innerAround -> proceed() -> targetMethod`. ## Zero calls (short-circuit) If an `@Around` returns without calling `proceed()`, the rest of the chain and the target never execute. The advice's return value becomes the method result. This is the basis for: - **caching** (return the cached value), - **authorization** (throw/deny before running), - **feature flags**, and - **stubbing**. ## Multiple calls `proceed()` may be called more than once; each call re-enters the *remaining* chain from that point. This enables: - **retry** (loop calling `proceed()` until success or attempts exhausted), and - **benchmarking** (call N times and average). Caveat: re-invoking side-effecting methods repeatedly can duplicate effects, so retry aspects must target idempotent operations or handle compensation. ## Arguments, return value and exceptions - **Argument overload.** `proceed(Object[] args)` advances the chain with substituted arguments (see argument-rewriting). The no-arg `proceed()` uses the original arguments. - **Return-value contract.** `proceed()` returns `Object` — the value produced by the downstream chain/target (possibly already transformed by inner advices). Your `@Around` must **return** it (optionally after transforming) or the caller receives `null`/wrong data. For `void` target methods `proceed()` returns `null`; returning `null` is fine there. - **Exception contract.** `proceed()` is declared `throws Throwable`. If the target or a downstream advice throws, that exception surfaces out of `proceed()`. You can let it propagate (typical), catch-and-translate, catch-and-recover (return a fallback), or catch-and-retry. Because it's `Throwable`, an `@Around` normally declares `throws Throwable` too. Swallowing exceptions silently is a common anti-pattern. ## Ordering Relative order of multiple aspects is set with `@Order`/`Ordered` (lower value = higher priority = outermost). The outermost advice's `proceed()` drives into the next; ordering determines wrapping (e.g. transaction outside vs metrics inside). Within a single aspect, Spring defines precedence among its advice kinds. Getting order wrong changes semantics (e.g. metrics counting or not counting retried attempts). ## Threading/reentrancy note The `ProceedingJoinPoint` is tied to a single invocation on the calling thread; if a retry spawns async work, `proceed()` semantics apply to the synchronous chain only. ## When it matters at scale Designing cross-cutting infrastructure (retry, circuit breaker, caching, tracing) hinges on correctly reasoning about: - `proceed()`'s chain position, - ordering vs other aspects, - idempotency for multi-proceed, and - disciplined return/exception handling.
- Does proceed() call the target method directly?Not necessarily. It advances to the next interceptor in the chain; only the final link invokes the real target. With several @Around advices, proceed() steps through each before the method runs.
- How do you control which of two @Around aspects wraps the other?With @Order or the Ordered interface — the lower order value is outermost (runs first, wraps the rest). This determines whether, say, transaction management sits outside or inside metrics/retry.
- What are the risks of calling proceed() multiple times?Each call re-executes the downstream chain, so non-idempotent side effects (writes, emails, payments) can be duplicated. Multi-proceed patterns (retry/benchmark) should target idempotent operations or add compensation.
saying these in an interview costs you the question
- Claiming proceed() always calls the target directly, ignoring the chain
- Thinking proceed() can only be called once
- Swallowing the Throwable from proceed() and returning null silently
- Assuming aspect order doesn't affect proceed() semantics