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.classfiles 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 anythingThe 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— inpackage-info.java.TYPE_USE(Java 8+) — any use of a type, including casts ((@NonNull String) o),extendsclauses, generic arguments. Used by nullability checkers like Checker Framework.TYPE_PARAMETER(Java 8+) — only on<T extends ...>declarations.MODULE(Java 9+) — inmodule-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 inheritanceCaveats:
- It only walks the superclass chain — interfaces do not propagate annotations even with
@Inherited.class Foo implements Auditable { }does not makeFoocarry an@Auditablefrom 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
valueelement 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) andgetAnnotationsByType(Schedule.class)(returns the array directly). Use the second on@Repeatableannotations.
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.
What to take from the run:
@Modulewas declared on the superclassReportingBase, but reflection found it onWeeklyReportsbecause 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:
WithInterfaceimplementsAnnotatedInterface, which has@Module, butgetAnnotation(Module.class)returnednull.@Inheritedonly walksextends, neverimplements. This trips up many newcomers; if you need cross-interface annotation visibility you have to walk the type tree yourself. runWeeklycarried two@Scheduleannotations.getAnnotationsByType(Schedule.class)returned a length-2 array — the right way to read repeated annotations. The container@Schedulesis invisible to user code if you stick togetAnnotationsByType.- The single-
@Schedulecase onrunDailywas symmetric:getAnnotation(Schedule.class)worked because there was no container, andgetAnnotationsByTypereturned a length-1 array. Either form is fine when you know the multiplicity. - The "repeated case via
getAnnotation" lines exposed the trap. OnrunWeekly,getAnnotation(Schedule.class)returnednull— the actual annotation in the class file is a synthesized@Schedulescontainer, not aSchedule. Reaching for the container viagetAnnotation(Schedules.class)does work. The rule: for any@Repeatableannotation, always usegetAnnotationsByTypeso the two cases (one occurrence vs. many) look identical.
Choosing your set
When you write a new annotation, decide all five at once:
- Who needs to read it? →
@Retention. - Where can it appear? →
@Target. - Should users see it in Javadoc? →
@Documentedor not. - Should subclasses inherit it? →
@Inheritedfor class-level markers like@Auditable. Skip for method-level annotations. - Should it apply more than once? →
@Repeatableif 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
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?