skip to content

How does ProceedingJoinPoint differ from JoinPoint, and where can you use it?

level: middleimportance: must knowfreq 75%

answer

  1. extends JoinPoint + proceed()
  2. only in @Around
  3. proceed() = continue the chain
  4. return type Object, throws Throwable
  5. forget to return proceed() = null/broken

basics

~20 s

ProceedingJoinPoint extends JoinPoint and adds proceed(), which actually invokes the advised method (or the next advice in the chain). It is valid only inside @Around advice. Other advice types get the plain JoinPoint, which cannot proceed.

solid answer

~40 s

ProceedingJoinPoint is a subtype of JoinPoint usable only in @Around advice. On top of the inspection methods (getArgs, getSignature, getTarget, getThis) it adds proceed() and proceed(Object[] args), which continue the invocation down the interceptor chain to the next advice or, ultimately, the real target method. This makes @Around the only advice that can decide whether the target runs at all: call proceed() to run it, skip it to short-circuit (caching, security), call it multiple times (retry), wrap it in timing, or transform its return value. proceed() returns the invocation result as Object and declares throws Throwable, so your @Around method typically returns Object and declares throws Throwable too. The other advice kinds run at a fixed point and never get proceed() — they can only observe, not control, the call.

code

java · 20 lines
java
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class TimingAspect {

    @Around("@annotation(com.katajob.Timed)")
    public Object time(ProceedingJoinPoint pjp) throws Throwable { // must be 1st param
        long start = System.nanoTime();
        try {
            return pjp.proceed();          // run the target; MUST return its result
        } finally {
            long ms = (System.nanoTime() - start) / 1_000_000;
            System.out.println(pjp.getSignature().getName() + " took " + ms + " ms");
        }
    }
}

go deeper

for a junior

Know proceed() runs the method and lives only in @Around.

for a middle

Explain the return-Object/throws-Throwable contract and short-circuit use cases.

for a senior

Discuss chain semantics (next advice vs target) and the must-return-proceed gotcha.

for a principal

Weigh @Around vs simpler advice on robustness and reason about interceptor-chain ordering.

**Recap.** A *join point* in Spring AOP is a method execution on a proxied bean. Advice is code attached to it. `org.aspectj.lang.JoinPoint` is the read-only handle to that execution given to `@Before`/`@After`/`@AfterReturning`/`@AfterThrowing`. **`ProceedingJoinPoint`.** `org.aspectj.lang.ProceedingJoinPoint` **extends** `JoinPoint`, so it has every inspection method (`getArgs`, `getSignature`, `getTarget`, `getThis`) **plus** two control methods: - **`Object proceed() throws Throwable`** — continues execution down the AOP *interceptor chain*: the next advice in order, and eventually the real target method. Returns whatever that downstream call returns. - **`Object proceed(Object[] args) throws Throwable`** — same, but you supply a replacement argument array, letting you rewrite the inputs the downstream call sees. **Why only `@Around`.** `@Around` is the only advice kind that *wraps* the join point rather than running at a fixed moment. Spring injects a `ProceedingJoinPoint` as the **first parameter** of an `@Around` method precisely so the advice can decide the fate of the call. The other advice kinds are positioned by Spring (before, after-returning, after-throwing, finally) and never surrender control of the invocation to your code, so they receive only `JoinPoint`. Declaring `ProceedingJoinPoint` in a non-`@Around` advice is a configuration error. **The control power of proceed():** - **Run normally:** `return pjp.proceed();` - **Short-circuit:** return a cached/authorized value *without* calling proceed() — the target never runs. - **Retry:** call proceed() in a loop until it succeeds. - **Around timing/metrics:** record `System.nanoTime()` before and after proceed(). - **Transform result:** call proceed(), then modify or wrap the returned Object. - **Rewrite inputs:** call proceed(newArgs) to change arguments (e.g. trimming/sanitizing). **Return-value and exception contract.** proceed() returns `Object` (the downstream result) and declares `throws Throwable`. Therefore an `@Around` method usually has return type `Object` and `throws Throwable`. **Critical gotcha:** whatever proceed() returns you must return from your advice — if you call proceed() but return something else (or forget to return it), the real caller gets the wrong value or `null`. Similarly, if you never call proceed(), the target method silently does not execute. **Signature shape.** A canonical `@Around` looks like `Object aroundX(ProceedingJoinPoint pjp) throws Throwable { ... return pjp.proceed(); }`. **When to reach for it.** Use `@Around` + `ProceedingJoinPoint` when you must *control* the call: caching, transactions-like wrapping, retries, circuit breaking, rate limiting, timing, argument sanitization, return-value transformation. If you only need to *observe* (log/audit/metrics on entry or exit), prefer a simpler advice with plain `JoinPoint` — it can't accidentally break the call by forgetting to proceed().

  • What happens if an @Around advice never calls proceed()?
    The target method (and any downstream advice) never executes. The advice's return value becomes the call result — useful for caching/security short-circuits, but a bug if unintended.
  • Why does an @Around method typically declare 'throws Throwable' and return Object?
    Because proceed() is declared to return Object and throw Throwable; matching those lets exceptions propagate naturally and passes the downstream return value through unchanged.

saying these in an interview costs you the question

  • Using ProceedingJoinPoint in @Before/@After
  • Calling proceed() but not returning its result
  • Assuming @Around runs the target automatically without proceed()
  • Thinking JoinPoint has a proceed() method

context