skip to content

What is the required method signature of an @Around advice, and how does its return type relate to the target method's return type (including void and primitives)?

level: middleimportance: should knowfreq 44%

answer

  1. ProceedingJoinPoint first param
  2. throws Throwable
  3. return type = Object (usually)
  4. void -> proceed() returns null
  5. primitive -> boxed; null = NPE at call site

basics

~20 s

The first parameter must be ProceedingJoinPoint and the method should declare throws Throwable. Return type is normally Object. proceed() returns the target's result as Object; for void targets it returns null, and you still return it.

solid answer

~50 s

An @Around method must take ProceedingJoinPoint as its first parameter (additional bound pointcut parameters may follow), and typically declares throws Throwable because proceed() does. Its return type is normally Object, and whatever it returns is handed to the caller as the method's result. proceed() returns Object; Spring unboxes/casts it to the target's actual return type when the caller receives it, so returning an incompatible type causes a ClassCastException at the call site. For a void target, proceed() returns null and you simply return null (or return proceed() directly). For primitive-returning targets, proceed() returns the boxed wrapper (e.g. Integer for int); you must return a value assignable to the primitive or you get an NPE/ClassCastException. You may declare a narrower return type than Object (e.g. String) if all matched methods return that type, but Object is the safe, general choice.

code

java · 12 lines
java
@Aspect
@Component
public class GenericAspect {

    // First param MUST be ProceedingJoinPoint; declare throws Throwable
    @Around("execution(* com.example..*(..))")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        Object result = pjp.proceed(); // Object; null for void; boxed for primitives
        // returning null here would NPE a primitive-returning target at the call site
        return result;
    }
}

go deeper

for a junior

Know the first parameter is ProceedingJoinPoint and the return type is Object.

for a middle

Explain throws Throwable, void->null, and boxing of primitive returns with the NPE/CCE pitfalls.

for a senior

Discuss narrowing the return type, bound pointcut parameters after ProceedingJoinPoint, and where cast failures surface.

for a principal

Design generic aspects defensively around typing so return-value transforms don't fail callers at runtime.

## Required signature ```java @Around("<pointcut>") public Object advice(ProceedingJoinPoint pjp[, <bound params>]) throws Throwable { ... return pjp.proceed(); } ``` Rules: 1. **First parameter is `ProceedingJoinPoint`** — mandatory, and it must be first. This is the `@Around`-only subtype of `JoinPoint` that adds `proceed()`. Using a plain `JoinPoint` gives you no `proceed()`. 2. **`throws Throwable`** — `proceed()` declares `throws Throwable`, so unless you fully handle it, your advice must declare it too. 3. **Return type** — normally `Object`. Additional parameters may follow the `ProceedingJoinPoint` if the pointcut binds them (e.g. `args(id)`, `@annotation(a)`, `this`, `target`). ## Return-type relationship - `proceed()` is typed `Object`; it returns whatever the target returned, **boxed** if primitive. - The value **your advice returns** becomes the method's result. Spring/AspectJ casts it back to the target's declared return type when returning to the caller. - Returning an object **incompatible** with the target's return type causes a **`ClassCastException`** at the call site (the caller expected `Foo`, you returned `Bar`). ## void targets For a `void` method, `proceed()` returns `null`. You still `return` (returning `null` or the result of `proceed()` is fine). You cannot change a void method's 'result' — there isn't one — but the wrapping (before/after/exception handling) still applies. ```java @Around("execution(void com.example.Mailer.send(..))") public Object aroundVoid(ProceedingJoinPoint pjp) throws Throwable { Object r = pjp.proceed(); // r is null for void return r; // returning null is correct } ``` ## Primitive-returning targets For `int foo()`, `proceed()` returns an `Integer` (autoboxed). If you construct the return value yourself, it must be assignable back to `int`: - Returning `null` for a primitive-returning method → **NullPointerException** when unboxed at the call site. - Returning a wrong wrapper type (e.g. `Long` where `int` expected) → `ClassCastException`. ## Narrower return types (optional) You may declare the advice return type more specifically than `Object` if **all** matched join points return that type: ```java @Around("execution(String com.example.*.*(..))") public String around(ProceedingJoinPoint pjp) throws Throwable { return (String) pjp.proceed(); } ``` This is legal but brittle — if the pointcut later matches a non-`String` method you get a cast failure. `Object` is the safe default. ## Gotchas - **Wrong first parameter type** (plain `JoinPoint`, or ProceedingJoinPoint not first) → aspect won't work / `IllegalArgumentException` at weaving. - **Forgetting `throws Throwable`** → won't compile if you call `proceed()` without handling. - **Returning null for primitive/void mismatch** → runtime NPE/CCE. - **Returning the wrong type** → `ClassCastException` surfaces to the caller, not the aspect. - Additional bound parameters must match the pointcut's binding designators exactly by name. ## When to care This matters whenever you transform the return value or write generic aspects over many methods — get the typing wrong and callers fail at runtime, not at aspect-authoring time.

  • What does proceed() return when the target method is void?
    null. There is no result to hand back, so proceed() yields null; you still return it (returning null is correct for a void target). The before/after/exception wrapping still applies.
  • You wrap an int-returning method and return null from the advice. What happens?
    A NullPointerException occurs when the null is unboxed to int at the caller's side. Primitive-returning targets require a non-null, type-compatible value; returning the wrong wrapper type instead gives a ClassCastException.

saying these in an interview costs you the question

  • Using plain JoinPoint expecting a proceed() method on it
  • Putting ProceedingJoinPoint anywhere but first
  • Assuming a void method's proceed() returns something other than null
  • Returning null for a primitive-returning target
  • Thinking a type mismatch fails in the aspect rather than at the caller (ClassCastException)

context