W3docs

Java Meta-Annotations

Annotations that annotate other annotations in Java — @Retention, @Target, @Documented, @Inherited, @Repeatable.

Java Meta-Annotations

A meta-annotation is an annotation that you put on an annotation declaration. They configure how that annotation behaves: how long it survives, where it's allowed to appear, whether subclasses inherit it, whether you can apply it more than once. There are five of them in java.lang.annotation, and every annotation type you ever write will use at least the first two.

The five:

  • @Retention — controls whether the annotation is kept in .class files and at runtime.
  • @Target — restricts the kinds of program elements the annotation can decorate.
  • @Documented — makes the annotation appear in generated Javadoc.
  • @Inherited — makes subclasses inherit class-level annotations from their superclass.
  • @Repeatable — allows the same annotation to be applied more than once to the same element.

@Retention

@Retention picks one of three RetentionPolicy values:

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.SOURCE)        // stripped by javac; never in bytecode
@interface SuppressEvenMoreWarnings { }

@Retention(RetentionPolicy.CLASS)         // in bytecode; not loaded by the VM (the default)
@interface BytecodeOnly { }

@Retention(RetentionPolicy.RUNTIME)       // in bytecode and accessible via reflection
@interface MyRuntimeMarker { }

The right choice depends on the consumer:

  • The compiler is the consumer (override checks, warning suppression, lint) → SOURCE.
  • A bytecode-processing tool, JIT optimiser, or post-compile analyser is the consumer → CLASS.
  • A framework reads the annotation at runtime via reflection (DI, ORM, JSON binding) → RUNTIME.

RUNTIME is the most permissive — it's also the most costly, because every annotation that survives adds bytes to your class file and a little reflection overhead at lookup time.

@Target

@Target restricts where the annotation can be placed. Its value is an array of ElementType constants:

import java.lang.annotation.ElementType;
import java.lang.annotation.Target;

@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
@interface Audited { }                                   // only on methods or constructors

@Target(ElementType.TYPE)
@interface Entity { }                                    // only on classes/interfaces/enums

@Target({})
@interface CannotBeApplied { }                           // exists only as a type — can't be used to annotate anything

The ElementType values you'll meet:

  • TYPE — class, interface, enum, annotation.
  • METHOD, CONSTRUCTOR, FIELD, PARAMETER, LOCAL_VARIABLE.
  • ANNOTATION_TYPE — for meta-annotations like the ones in this chapter.
  • PACKAGE — in package-info.java.
  • TYPE_USE (Java 8+) — any use of a type, including casts ((@NonNull String) o), extends clauses, generic arguments. Used by nullability checkers like Checker Framework.
  • TYPE_PARAMETER (Java 8+) — only on <T extends ...> declarations.
  • MODULE (Java 9+) — in module-info.java.
  • RECORD_COMPONENT (Java 16+) — on the parameters of a record header.

If you omit @Target entirely the annotation can go almost anywhere — useful for very general markers, restrictive for everything else. Almost always set an explicit @Target.

@Documented

By default, annotations are not shown in the Javadoc of the elements they decorate. @Documented opts an annotation into being included:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface ApiNote {
  String value();
}

If a method carries @ApiNote("rate-limited to 5 rps"), Javadoc will show that annotation in the rendered docs. Without @Documented, the annotation exists at runtime but is invisible in the generated documentation. Add @Documented to anything you expect users of your library to see.

@Inherited

@Inherited applies only to annotations targeting TYPE (classes). It says: if a class is annotated, its subclasses are treated as annotated too. Reflection's getAnnotation(...) will walk up the superclass chain and find it.

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface Auditable { }

@Auditable
class Account { }

class SavingsAccount extends Account { }                 // also "auditable" via inheritance

Caveats:

  • It only walks the superclass chain — interfaces do not propagate annotations even with @Inherited. class Foo implements Auditable { } does not make Foo carry an @Auditable from the interface.
  • It only affects how reflection reports annotations on classes. Methods, fields, and parameters never inherit annotations from overridden members.

Most frameworks now prefer explicit, repeated annotations over inheritance because the rules are simpler. Use @Inherited only when "anything that extends a marker class is also marked" is genuinely what you want.

@Repeatable

Before Java 8, you could not apply the same annotation twice to the same element. @Repeatable lifts that restriction, but the mechanics need care. You declare a container annotation that holds an array of the repeated values, and point @Repeatable at the container:

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface Schedules { Schedule[] value(); }            // the container

@Repeatable(Schedules.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface Schedule { String cron(); }                  // the repeated annotation

class Reports {
  @Schedule(cron = "0 9 * * MON")
  @Schedule(cron = "0 9 * * FRI")
  public void weekly() { /* ... */ }
}

Rules:

  • The container's value element must be an array of the repeated annotation type.
  • The container and the repeated annotation should have the same retention and at least the same targets.
  • At runtime, the compiler bundles repeated uses into a single container annotation. Reflection has both getAnnotation(Schedule.class) (returns the single container element when there are two) and getAnnotationsByType(Schedule.class) (returns the array directly). Use the second on @Repeatable annotations.

A worked example: putting all five meta-annotations on a real annotation

The example builds a small annotation system end-to-end: a @Schedule that's RUNTIME, methods-only, documented, and repeatable; a marker @Module that's inherited by subclasses. The main method then reads them via reflection.

java— editable, runs on the server

What to take from the run:

  • @Module was declared on the superclass ReportingBase, but reflection found it on WeeklyReports because the annotation carries @Inherited. The lookup walks the class hierarchy until it finds the annotation or runs out of superclasses.
  • The interface case showed the inheritance limit: WithInterface implements AnnotatedInterface, which has @Module, but getAnnotation(Module.class) returned null. @Inherited only walks extends, never implements. This trips up many newcomers; if you need cross-interface annotation visibility you have to walk the type tree yourself.
  • runWeekly carried two @Schedule annotations. getAnnotationsByType(Schedule.class) returned a length-2 array — the right way to read repeated annotations. The container @Schedules is invisible to user code if you stick to getAnnotationsByType.
  • The single-@Schedule case on runDaily was symmetric: getAnnotation(Schedule.class) worked because there was no container, and getAnnotationsByType returned a length-1 array. Either form is fine when you know the multiplicity.
  • The "repeated case via getAnnotation" lines exposed the trap. On runWeekly, getAnnotation(Schedule.class) returned null — the actual annotation in the class file is a synthesized @Schedules container, not a Schedule. Reaching for the container via getAnnotation(Schedules.class) does work. The rule: for any @Repeatable annotation, always use getAnnotationsByType so the two cases (one occurrence vs. many) look identical.

Choosing your set

When you write a new annotation, decide all five at once:

  1. Who needs to read it? → @Retention.
  2. Where can it appear? → @Target.
  3. Should users see it in Javadoc? → @Documented or not.
  4. Should subclasses inherit it? → @Inherited for class-level markers like @Auditable. Skip for method-level annotations.
  5. Should it apply more than once? → @Repeatable if and only if you genuinely need multiplicity.

The default skeleton for a runtime-visible method annotation:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
@interface MyAnnotation { /* elements */ }

The next chapter — Custom annotations — uses this exact pattern to build one from scratch and consume it.

Practice

Practice

You declare `@Tagged` with `@Inherited` and `@Target(ElementType.TYPE)`. `interface Marker {}` is annotated `@Tagged`, and `class Concrete implements Marker {}`. What does `Concrete.class.getAnnotation(Tagged.class)` return?