Implementing Custom Annotations

1. Creating Custom Annotations (@interface)

Example: Custom @Auditable meta-annotation

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Auditable {
  String action();
  Severity severity() default Severity.INFO;
}

2. Using Meta-Annotations

Meta-annotation Use
@Target Where annotation can be applied (TYPE, METHOD, FIELD, PARAMETER)
@Retention SOURCE / CLASS / RUNTIME
@Documented Include in Javadoc
@Inherited Subclasses inherit
@Repeatable Apply multiple times

3. Implementing Annotation Processors

Type Purpose
Compile-time (APT) Code generation (Lombok, MapStruct)
Runtime (reflection) Configure / behavior at runtime
Spring AOP Common runtime path for behavioral annotations

4. Using AOP for Annotation Processing (@Aspect)

Example: AOP audit advice with execution time

@Aspect
@Component
public class AuditAspect {
  @Around("@annotation(audit)")
  public Object audit(ProceedingJoinPoint pjp, Auditable audit) throws Throwable {
    long start = System.nanoTime();
    try { return pjp.proceed(); }
    finally {
      long ms = (System.nanoTime() - start) / 1_000_000;
      log.info("AUDIT action={} ms={}", audit.action(), ms);
    }
  }
}

5. Creating Validation Annotations

Example: @StrongPassword validation annotation

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = StrongPasswordValidator.class)
public @interface StrongPassword {
  String message() default "Password must contain upper, lower, digit, symbol";
  Class<?>[] groups() default {};
  Class<? extends Payload>[] payload() default {};
}

6. Creating Security Annotations

Example: Composed @AdminOrOwner security annotation

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("hasRole('ADMIN') or @secService.isOwner(#id, authentication)")
public @interface AdminOrOwner {}

@AdminOrOwner
public Order get(Long id) { /* … */ }

7. Creating Logging Annotations

Example: Log execution time with AOP aspect

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecutionTime { String name() default ""; }

@Aspect @Component
public class LogExecutionTimeAspect {
  @Around("@annotation(t)")
  public Object around(ProceedingJoinPoint pjp, LogExecutionTime t) throws Throwable {
    long start = System.nanoTime();
    try { return pjp.proceed(); }
    finally { log.info("{} took {} ms", t.name().isBlank() ? pjp.getSignature() : t.name(),
                       (System.nanoTime() - start)/1_000_000); }
  }
}

8. Using SpEL in Custom Annotations

Example: SpEL key evaluation in rate limit aspect

public @interface RateLimit { String key(); int limit(); }

@Around("@annotation(rl)")
public Object enforce(ProceedingJoinPoint pjp, RateLimit rl) throws Throwable {
  ExpressionParser parser = new SpelExpressionParser();
  StandardEvaluationContext ctx = new StandardEvaluationContext();
  // … bind args by name with ParameterNameDiscoverer
  String key = parser.parseExpression(rl.key()).getValue(ctx, String.class);
  // throttle by key …
  return pjp.proceed();
}

9. Combining Multiple Annotations (@AliasFor)

Example: @AliasFor composed request mapping annotation

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@RequestMapping(method = RequestMethod.POST, consumes = "application/json")
public @interface PostJson {
  @AliasFor(annotation = RequestMapping.class, attribute = "value")
  String[] value() default {};
}

10. Testing Custom Annotations

Approach Use
Reflection Assert annotation present + values
@SpringBootTest Verify aspect intercepts
AOP proxy check AopUtils.isAopProxy(bean)