skip to content

What does the @annotation pointcut designator match in Spring AOP, and how is it different from designators like execution() or within()?

level: juniorimportance: must knowfreq 55%

answer

  1. method carries the annotation
  2. opt-in by annotation, not by name
  3. RUNTIME retention mandatory
  4. static match, no runtime residue
  5. method annotations not inherited

basics

~10 s

@annotation(SomeAnnotation) matches any method-execution join point where the method being called carries that annotation. Unlike execution()/within(), which match by name or package pattern, @annotation matches by the presence of an annotation on the method.

solid answer

~40 s

@annotation is an annotation-presence pointcut designator: @annotation(com.example.Audited) matches a method-execution join point when the executing method is annotated with @Audited. execution() and within() select join points by signature/type-name patterns; @annotation selects them by the metadata (annotation) present on the method itself, so advice applies wherever developers opt in by adding the annotation, regardless of package or name. The annotation's @Retention must be RUNTIME, or Spring cannot see it. Because it targets the method, it is resolved statically (the method is known at proxy-creation time). It is the conceptual basis for how declarative features like @Transactional and @Cacheable match method-level annotations. You can also bind the annotation instance as an advice parameter to read its attributes.

code

java · 15 lines
java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Audited { String level() default "INFO"; }

@Aspect
@Component
public class AuditAspect {

    // Matches ANY method annotated with @Audited, regardless of class/package.
    @Around("@annotation(audited)")
    public Object audit(ProceedingJoinPoint pjp, Audited audited) throws Throwable {
        log.info("[{}] {}", audited.level(), pjp.getSignature().toShortString());
        return pjp.proceed();
    }
}

go deeper

for a junior

Know that @annotation matches methods that carry a given annotation, and that retention must be RUNTIME.

for a middle

Contrast with execution()/within(); know it is a static matcher and that you can bind the annotation to read attributes.

for a senior

Articulate the opt-in design, the non-inheritance of method annotations, and how this underpins @Transactional/@Cacheable method-level matching.

for a principal

Discuss trade-offs vs @within/@target for API design, retention/meta-annotation strategy, and why annotation-based designators decouple aspects from package structure.

## What it is Spring AOP pointcut expressions use AspectJ's pointcut language. Besides the common `execution(...)`, `within(...)`, `args(...)` designators, AspectJ provides four **annotation-presence** designators: `@annotation`, `@within`, `@target`, and `@args`. They select join points based on where an annotation is present rather than on class/method names. `@annotation(SomeAnnotation)` matches a **method-execution join point when the method being executed is directly annotated** with `SomeAnnotation`. (In Spring AOP the only join point kind is method execution — there is no field or constructor weaving.) ```java @Retention(RetentionPolicy.RUNTIME) // MANDATORY — Spring reads annotations reflectively at runtime @Target(ElementType.METHOD) public @interface Audited {} @Aspect @Component public class AuditAspect { @Before("@annotation(com.example.Audited)") public void audit(JoinPoint jp) { /* runs before any @Audited method */ } } ``` ## How it differs from execution()/within() - `execution(* com.example.service.*.*(..))` selects by **method signature pattern** (return type, package, class, name, args). - `within(com.example.service..*)` selects by the **type** in which the join point occurs (name pattern). - `@annotation(X)` selects by the **annotation present on the method** — no name or package coupling. Developers opt in per method by adding `@X`. This inversion of control is why annotation designators scale so well: the aspect declares *what capability* to apply, and any method across the codebase can request it by adding the annotation. ## Key requirements and mechanics - **Retention must be `RUNTIME`.** With `SOURCE` or `CLASS` retention the annotation is invisible at runtime and never matches. - `@annotation` is a **static** matcher: whether a method carries the annotation is known when Spring builds the proxy, so there is no per-invocation runtime residue check (unlike `@target`/`@args`). - **Java method annotations are NOT inherited.** If a subclass overrides an annotated method without re-declaring the annotation, `@annotation` no longer matches that overriding method. (Spring's own `@Transactional`/`@Cacheable` support works around this with `AnnotatedElementUtils`/merged-annotation lookup, but a raw `@annotation` pointcut does not.) ## Reading annotation attributes You can **bind** the annotation instance to an advice parameter whose name matches the designator reference, then read its attributes: ```java @Around("@annotation(audited)") public Object around(ProceedingJoinPoint pjp, Audited audited) throws Throwable { String level = audited.level(); // read attribute return pjp.proceed(); } ``` ## When to use Use `@annotation` for **cross-cutting behavior that individual methods opt into**: auditing, custom caching, retry, rate-limiting, metrics timing. It is the cleanest, most explicit of the annotation designators and the safest performance-wise.

  • Why must the annotation have RUNTIME retention?
    Spring AOP inspects annotations reflectively at runtime when matching pointcuts and creating proxies. With SOURCE or CLASS retention the annotation is discarded before runtime, so it is never visible and the pointcut can never match.
  • If a subclass overrides a method annotated with @Audited but doesn't repeat the annotation, does @annotation still match?
    No. Java method-level annotations are not inherited, so the overriding method has no @Audited and the pointcut skips it. You'd have to re-declare the annotation on the override.

saying these in an interview costs you the question

  • Claiming @annotation matches when the annotation is on the class (that's @within/@target)
  • Saying SOURCE/CLASS retention works for AOP matching
  • Believing method annotations are inherited by overrides
  • Confusing @annotation with the @Annotation-on-parameter idea (that's @args)

context