skip to content

For an argument-validation gate, would you use @Before or @Around, and why?

level: seniorimportance: should knowfreq 45%

answer

  1. Accept/reject → @Before (throw to reject)
  2. Skip / rewrite args / change return / catch → @Around
  3. @Before can't substitute a return, only throw
  4. @Around: proceed() or forget-and-drop hazard
  5. Constraint = clearer intent for validators

basics

~20 s

Use @Before for pure validation that either passes or throws — it's simpler and clearly signals 'no invocation control.' Use @Around only when you must skip the call, replace arguments, alter the return value, or handle exceptions.

solid answer

~50 s

For a validation gate that just accepts or rejects, @Before is the right, minimal tool: it runs before the join point, reads arguments via JoinPoint or args() binding, and throws (e.g. IllegalArgumentException or a domain exception) to abort. Throwing is a first-class way to stop the target — the exception propagates to the caller and the method never runs. @Before also communicates intent: it structurally *cannot* proceed conditionally or mutate the return, so readers know it only inspects. Reach for @Around only when you need capabilities @Before lacks: conditionally skipping the target (return a cached/short-circuit result without calling it), rewriting arguments via proceed(newArgs), transforming or wrapping the return value, or catching/translating exceptions around the call. @Around is more powerful but heavier and error-prone (you must remember to call proceed and handle Throwable), so prefer @Before when validation is all you need.

code

java · 14 lines
java
// Validation gate: @Before is enough — accept (return) or reject (throw)
@Aspect
@Component
@Order(0) // run before logging/timing aspects
public class OrderValidationAspect {

    @Before("execution(* com.app.OrderService.place(..)) && args(order)")
    public void validate(Order order) {
        if (order.getItems().isEmpty()) {
            throw new IllegalArgumentException("order must have at least one item");
        }
        // returning normally lets OrderService.place(order) run
    }
}

go deeper

for a junior

Know @Before can reject by throwing; @Around is the 'more powerful' one.

for a middle

Explain that skipping/rewriting/return-changing requires @Around, throw-only fits @Before.

for a senior

Justify choosing the least-powerful advice for intent clarity and testability; know the proceed() hazards.

for a principal

Set team guidance: prefer @Before for gates, reserve @Around for genuine invocation control; weigh AOP validation vs bean-validation and ordering across aspects.

## The decision, stated plainly - **Pure validation (pass or reject):** use **`@Before`**. - **Anything that needs to alter the invocation** (skip it, change args, change/wrap the return, catch its exceptions): use **`@Around`**. ## Why @Before works for validation A validation gate has exactly two outcomes: *the arguments are acceptable → let the call proceed*, or *they aren't → stop it*. `@Before` maps cleanly onto this: - If the advice returns normally, Spring proceeds to the target automatically. That is the "accept" path — you write nothing. - If the advice **throws**, the exception propagates to the caller and the target is never invoked. That is the "reject" path. Throwing is the *only* mechanism `@Before` has to stop the call, and here it's exactly what you want. ```java @Before("execution(* com.app.OrderService.place(..)) && args(order)") public void validate(Order order) { if (order.getItems().isEmpty()) throw new IllegalArgumentException("order must have items"); } ``` ### Design benefit: intent through constraint `@Before` *cannot* proceed conditionally, cannot mutate the return, cannot swallow the target's exceptions. That limitation is a feature for a validator: anyone reading the aspect knows it only inspects and possibly aborts. It's simpler to reason about, simpler to test, and there's no `proceed()` to forget. ## Why you'd move to @Around `@Around` receives a `ProceedingJoinPoint` and decides *if/how* the target runs. Choose it when validation is not the whole story: 1. **Short-circuit / skip the call** — e.g. return a cached value or a default without invoking the target. `@Before` can't do this (it can only throw, not return a substitute). 2. **Rewrite arguments** — call `pjp.proceed(newArgs)` with sanitized/normalized arguments. `@Before` mutating `getArgs()` is ignored. 3. **Transform the return value** — wrap, redact, or convert what the method returns. 4. **Exception handling around the call** — try/catch `proceed()` to translate or suppress exceptions, add retries, timing on both success and failure paths. ```java @Around("execution(* com.app.PriceService.quote(..))") public Object aroundQuote(ProceedingJoinPoint pjp) throws Throwable { if (cache.has(key(pjp))) return cache.get(key(pjp)); // skip target Object result = pjp.proceed(); // or proceed(newArgs) cache.put(key(pjp), result); return result; } ``` ## Tradeoffs / gotchas - **Power vs. safety:** `@Around` must declare `throws Throwable`, must remember to call `proceed()` (forgetting silently drops the call), and must correctly return `proceed()`'s result. `@Before` has none of these hazards. - **Performance:** negligible difference for validation; both are proxy interceptions. - **Exception type:** whether you throw `IllegalArgumentException`, `ConstraintViolationException`, or a domain exception is a separate design choice; in Spring MVC you'd typically map it via `@ExceptionHandler`/`@ControllerAdvice`. - **Ordering:** if a validation `@Before` and other advice share a join point, use `@Order` so validation runs first (before logging/timing). - **Don't overuse AOP for validation** — bean validation (`@Valid` + JSR-380 annotations) or explicit guard clauses are often clearer for simple field checks; AOP validation shines for cross-cutting, policy-style rules across many methods. ## One-line rule If you only ever *accept or reject*, use `@Before`. The moment you need to *change what happens to the call or its result*, use `@Around`.

  • Your @Before validator needs to normalize an argument (trim/lowercase) before the call. Can it?
    No — @Before can't substitute arguments; the target gets the originals. Switch to @Around and call proceed(newArgs) with the normalized values.
  • What's a risk unique to @Around that @Before avoids?
    Forgetting to call proceed() (silently dropping the invocation) or mishandling its return/Throwable. @Before has no proceed and can't accidentally swallow the call or its result.

saying these in an interview costs you the question

  • Reaching for @Around 'to be safe' when a throw-only validator suffices
  • Claiming @Before can return a substitute value to skip the target
  • Trying to normalize/rewrite args in @Before and expecting it to take effect

context