Meta-Annotations in Java
A meta-annotation is an annotation that is placed on another annotation's declaration. It tells Java how that annotation behaves: how long it is kept, where it may be written, whether subclasses inherit it, and whether it can be repeated. Java provides five of them in java.lang.annotation: @Retention, @Target, @Documented, @Inherited and @Repeatable. In this lesson, you will learn what each one does, with programs that show the difference.
The Five Meta-Annotations
| Meta-annotation | It is used to... |
|---|---|
@Retention | say how long the annotation is kept: source only, class file, or run time. |
@Target | say which kinds of declarations the annotation may be written on. |
@Documented | include the annotation in the generated Javadoc of annotated elements. |
@Inherited | let subclasses inherit a class-level annotation from their superclass. |
@Repeatable | allow the same annotation to be written more than once on one element. |
The examples below declare small annotations with @interface. The full syntax is explained in "Custom Annotations in Java".
@Retention: How Long Is It Kept?
| Policy | Kept in source | Kept in .class file | Readable at run time | Typical use |
|---|---|---|---|---|
RetentionPolicy.SOURCE | Yes | No | No | Compiler checks: @Override, @SuppressWarnings |
RetentionPolicy.CLASS (the default) | Yes | Yes | No | Bytecode tools |
RetentionPolicy.RUNTIME | Yes | Yes | Yes | Frameworks: Spring, JUnit, JPA |
Let's see an example that puts one annotation of each kind on a class and asks which are visible at run time:
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
public class Ret {
@Retention(RetentionPolicy.SOURCE)
@interface InSource { }
@Retention(RetentionPolicy.CLASS)
@interface InClassFile { }
@Retention(RetentionPolicy.RUNTIME)
@interface AtRunTime { }
@InSource
@InClassFile
@AtRunTime
static class Report { }
public static void main(String[] args) {
Class<Report> c = Report.class;
System.out.println("SOURCE visible? "
+ c.isAnnotationPresent(InSource.class));
System.out.println("CLASS visible? "
+ c.isAnnotationPresent(InClassFile.class));
System.out.println("RUNTIME visible? "
+ c.isAnnotationPresent(AtRunTime.class));
}
}SOURCE visible? false
CLASS visible? false
RUNTIME visible? trueExplanation: Only the RUNTIME annotation can be read by the running program. The CLASS one is still in the file, though, as javap -v shows. It is stored in a separate "invisible" list that the JVM doesn't load:
RuntimeVisibleAnnotations:
0: #14()
Ret$AtRunTime
RuntimeInvisibleAnnotations:
0: #16()
Ret$InClassFileInSource appears nowhere: the compiler dropped it.
@Target: Where May It Be Written?
@Target lists the allowed places as ElementType constants:
ElementType | Allows the annotation on |
|---|---|
TYPE | Classes, interfaces, enums, records, annotations |
FIELD | Fields and enum constants |
METHOD | Methods |
PARAMETER | Method and constructor parameters |
CONSTRUCTOR | Constructors |
LOCAL_VARIABLE | Local variables |
ANNOTATION_TYPE | Other annotation declarations |
PACKAGE, MODULE | Package and module declarations |
TYPE_PARAMETER, TYPE_USE | Type parameters, and any use of a type, such as List<@NonNull String> |
RECORD_COMPONENT | Record components |
Several can be given in braces: @Target({ElementType.METHOD, ElementType.FIELD}). Using an annotation in a place its target doesn't allow is a compile error:
@Target(ElementType.METHOD)
@interface Audited { }
@Audited
static class Account { }Tg.java:8: error: annotation interface not applicable to this kind of declaration
@Audited
^
1 errorWithout @Target, an annotation can be used on any declaration.
@Inherited: Passing to Subclasses
By default, a subclass does not get its superclass's annotations. @Inherited changes that for class-level annotations:
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
public class Inh {
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@interface Entity { }
@Retention(RetentionPolicy.RUNTIME)
@interface Audited { }
@Entity
@Audited
static class Person { }
static class Student extends Person { }
public static void main(String[] args) {
System.out.println("Student has @Entity? "
+ Student.class.isAnnotationPresent(Entity.class));
System.out.println("Student has @Audited? "
+ Student.class.isAnnotationPresent(Audited.class));
}
}Student has @Entity? true
Student has @Audited? falseExplanation: Student has no annotations of its own. Because @Entity is @Inherited, asking Student for it finds the one on Person. @Audited isn't inherited. @Inherited works only for classes: annotations on interfaces aren't inherited by implementing classes, and annotations on methods aren't inherited by overriding methods.
@Repeatable: Using It More Than Once
Normally the same annotation can appear only once on an element. To allow repeats, you declare a container annotation that holds an array, and name it in @Repeatable:
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
public class Rep {
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(Schedules.class)
@interface Schedule {
String day();
}
@Retention(RetentionPolicy.RUNTIME)
@interface Schedules {
Schedule[] value();
}
@Schedule(day = "Monday")
@Schedule(day = "Thursday")
static class MathClass { }
public static void main(String[] args) {
Schedule[] all =
MathClass.class.getAnnotationsByType(Schedule.class);
for (Schedule s : all) {
System.out.println("class on " + s.day());
}
System.out.println("direct @Schedule: "
+ MathClass.class.getAnnotation(Schedule.class));
System.out.println("container: "
+ MathClass.class.getAnnotation(Schedules.class));
}
}class on Monday
class on Thursday
direct @Schedule: null
container: @Rep.Schedules({@Rep.Schedule(day="Monday"), @Rep.Schedule(day="Thursday")})Explanation: The compiler wraps the two @Schedule annotations inside one hidden @Schedules. That's why getAnnotation(Schedule.class) returns null: there is no single direct @Schedule. Use getAnnotationsByType, which looks inside the container. Without @Repeatable, writing an annotation twice fails with "Tag is not a repeatable annotation interface".
@Documented: Show It in Javadoc
By default, Javadoc doesn't show which annotations a class or method carries. If the annotation is part of the public contract, such as "this method is experimental", mark its declaration with @Documented and it appears in the generated documentation of every element that uses it. @Deprecated and @FunctionalInterface are both @Documented.
Common Mistakes
Forgetting @Retention(RUNTIME). The annotation compiles and is silently invisible to reflection.
Expecting @Inherited to work for methods or interfaces. It applies only to annotations on classes, through extends.
Calling getAnnotation on a repeated annotation. It returns null; use getAnnotationsByType.
Giving the container a shorter retention than the repeatable annotation. The container must be kept at least as long.
Interview Corner
What are meta-annotations in Java?
Meta-annotations are annotations applied to annotation declarations. @Retention sets how long an annotation is kept (SOURCE, CLASS, which is the default, or RUNTIME). @Target restricts where it may be used through ElementType values. @Documented makes it appear in Javadoc. @Inherited lets subclasses inherit a class-level annotation. @Repeatable allows several uses on one element by naming a container annotation.
What is the difference between the retention policies?
SOURCE annotations are discarded by the compiler and exist only for compile-time checks. CLASS annotations are written to the class file but not made available at run time. RUNTIME annotations are in the class file and loaded by the JVM, so they can be read with reflection; frameworks need this policy.
Practice
- Easy: Change
AtRunTimeinRetto have no@Retentionat all, and check whether it is still visible. - Harder: Create a repeatable
@Author(name = ...)annotation for classes, apply it three times, and print the names. Then make it@Inheritedand test on a subclass.
Key Takeaways
Was this tutorial helpful?