skip to content

How do you bind an annotation instance to advice so you can read its attributes, and what are the parameter-name rules?

level: middleimportance: should knowfreq 40%

answer

  1. name a variable, not the class
  2. advice param type = annotation type, same name
  3. -parameters flag / argNames fallback
  4. JoinPoint first if present
  5. binding = read attributes = configurable aspect

basics

~20 s

Reference the annotation by a lowercase name in the designator, e.g. @annotation(retry), and declare an advice parameter of that annotation type with the same name (Retry retry). Spring injects the actual annotation instance so you can call its attribute methods.

solid answer

~50 s

Instead of naming the annotation class, name a *binding variable*: @annotation(retry). Then the advice method declares a parameter of the annotation type with the matching name — Object around(ProceedingJoinPoint pjp, Retry retry). Spring passes the real annotation instance found on the join point, so you can read attributes like retry.maxAttempts(). The parameter name must match the designator reference. Since Java 8+ with -parameters (Spring Boot compiles with it) names are recovered from bytecode; otherwise add argNames in the advice annotation or Spring may fail to bind. The same binding works for @within, @target (annotation on the type) and @args (annotation on the runtime argument type). Any join-point form (JoinPoint or ProceedingJoinPoint) must be the first parameter if present. This binding is how you build configurable aspects — retry counts, cache names, rate limits — directly from annotation metadata.

code

java · 15 lines
java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RateLimited { double permitsPerSecond(); }

@Aspect @Component
class RateLimitAspect {

    // 'limited' binds the annotation instance; name matches the parameter.
    @Around(value = "@annotation(limited)", argNames = "pjp,limited")
    public Object throttle(ProceedingJoinPoint pjp, RateLimited limited) throws Throwable {
        double rate = limited.permitsPerSecond();   // attribute drives behavior
        rateLimiterFor(pjp, rate).acquire();
        return pjp.proceed();
    }
}

go deeper

for a junior

Know you can pass the annotation into advice to read its values.

for a middle

State the name-matching rule and the annotation-type parameter; know JoinPoint must be first.

for a senior

Explain -parameters vs argNames, binding across all four designators, and how this yields configurable, reusable aspects.

for a principal

Relate to how Spring's metadata sources read @Transactional/@Cacheable attributes; discuss build-config guarantees (-parameters) as a cross-cutting concern for aspect reliability.

## The two ways to write an annotation designator 1. **By class literal (no binding):** `@annotation(com.example.Retry)` — matches, but you get no handle to the annotation instance. 2. **By binding variable:** `@annotation(retry)` — the token `retry` is a *variable* the advice will receive. You then declare a parameter of the annotation type with the same name. ```java @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Retry { int maxAttempts() default 3; } @Aspect @Component class RetryAspect { @Around("@annotation(retry)") // 'retry' is a binding var public Object retry(ProceedingJoinPoint pjp, Retry retry) throws Throwable { int attempts = retry.maxAttempts(); // read the attribute RuntimeException last = null; for (int i = 0; i < attempts; i++) { try { return pjp.proceed(); } catch (RuntimeException e) { last = e; } } throw last; } } ``` ## Parameter-name matching rules - The **variable name in the pointcut must equal the advice parameter name** (`retry` ↔ `Retry retry`). - Spring recovers parameter names from bytecode only if the code is compiled with the **`-parameters`** flag (Spring Boot's Gradle/Maven plugins enable this by default) or debug info. If names are unavailable, Spring cannot infer the binding and throws `IllegalArgumentException` about argument names. - You can be explicit with the **`argNames`** attribute: `@Around(value = "@annotation(retry)", argNames = "retry")`. The `JoinPoint`/`ProceedingJoinPoint`/`JoinPoint.StaticPart` parameters do **not** need to be listed in `argNames`. - If the advice has a `JoinPoint` or `ProceedingJoinPoint` parameter, it must be **declared first**. ## Binding works for all four annotation designators - `@annotation(x)` → binds the annotation present on the **method**. - `@within(x)` / `@target(x)` → binds the annotation present on the **type** (declaring or runtime type respectively). - `@args(x)` → binds the annotation present on the **runtime type of the argument**. In every case the bound parameter type must be the annotation type, and the count/positions must be consistent with the designator. ## Common mistakes - Mixing forms: `@annotation(com.example.Retry)` (class literal) while also declaring a `Retry retry` parameter — the class-literal form does not bind, so binding fails. - Name mismatch between the pointcut variable and the parameter. - Forgetting `-parameters` in a non-Boot build and getting binding errors at startup. ## Why it matters Binding turns annotations into **configuration**: the aspect reads `maxAttempts`, `cacheNames`, `permitsPerSecond`, etc. from each method's annotation, so one aspect serves many differently-configured call sites. This is exactly how Spring's own `@Cacheable`/`@Transactional` infrastructure reads attributes (via its own metadata sources) to drive behavior.

  • What error appears if parameter names can't be resolved and you didn't set argNames?
    Spring throws IllegalArgumentException / AopConfigException complaining it cannot determine argument names (e.g. 'requires the parameter names to be specified'). Fix by compiling with -parameters or adding argNames to the advice annotation.
  • Can you bind the annotation with @within or @target too?
    Yes. @within(x) and @target(x) bind the type-level annotation instance (declaring type vs runtime type). @args(x) binds the annotation on the runtime argument's type. Same name-matching rules apply.

saying these in an interview costs you the question

  • Using the class-literal form and expecting a bound parameter
  • Thinking any parameter name works regardless of the pointcut token
  • Putting JoinPoint after the bound annotation parameter
  • Assuming binding works without -parameters or argNames in a plain (non-Boot) build

context