Custom annotations in Java
@interface, elements, @Retention, @Target, @Inherited, @Repeatable.
Declaring your own
An annotation type is declared with **@interface. Its elements look like methods and can have defaults**.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface Timed {
String value() default "";
int warnMs() default 500;
}What elements may be
Element types are limited to **primitives, String, Class, enums, other annotations, and arrays of these**. No wrapper types like Integer, no List, and no null defaults.
Integer is not allowed
Wrapper types aren't on the list, so this is a compile error. Use the primitive instead: int port() default 8080;
@interface Config {
Integer port() default 8080; // error
}Retention and target
**@Retention says how long it's kept: SOURCE (javac only), CLASS (the default: stored in the .class file, but invisible to reflection) or RUNTIME (readable via reflection). @Target** says where it may go: METHOD, TYPE, FIELD and so on.
Count what reflection sees
What does this print?
@Retention(RetentionPolicy.SOURCE)
@interface Note { }
@interface Plain { }
@Retention(RetentionPolicy.RUNTIME)
@interface Kept { }
@Note @Plain @Kept class Box { }
void main() {
int n = Box.class.getAnnotations().length;
System.out.println(n);
}310
Show the answer
Only **@Kept** has RUNTIME retention. @Note is SOURCE (gone after javac). @Plain has no @Retention, so it defaults to CLASS: stored in the file, but invisible to reflection.
@Inherited and @Repeatable
**@Inherited: a class annotation on a superclass is also found when you ask the subclass. @Repeatable: the annotation may appear several times on one element, but it needs a container** annotation with an array value(). Read repeats with getAnnotationsByType.
@Repeatable(Roles.class)
@interface Role { String value(); }
@interface Roles { Role[] value(); }
@Role("admin") @Role("dev")
class Ops { }Silent failures
Custom annotations power @Timed metrics, validation like @NotBlank and @Transactional. The classic bug: forgetting @Retention(RUNTIME). Nothing fails; the framework simply sees nothing, and your metrics stay empty.
Key takeaways
- Retention: SOURCE, CLASS (the default) or RUNTIME
- Only RUNTIME annotations are visible to reflection
- @Inherited works for class annotations from superclasses
- @Repeatable needs a container annotation
If an annotation's only element is named value, you can leave out the name: @Role("admin") instead of @Role(value = "admin").
Practice questions
What does this print?
@interface Plain { }
@Retention(RetentionPolicy.RUNTIME)
@interface Kept { }
@Plain @Kept class Box { }
void main() {
var anns = Box.class.getAnnotations();
System.out.println(anns.length);
}- 2
- 1
- 0
Check your answer
1. @Kept is RUNTIME, so reflection sees it. @Plain has no @Retention, so it defaults to CLASS: stored in the .class file but invisible at runtime.
What does this print?
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@interface Team { String value(); }
@Team("core") class Base { }
class Sub extends Base { }
void main() {
var t = Sub.class.getAnnotation(Team.class);
System.out.println(t.value());
}- core
- Throws NullPointerException
- Compile error
- Sub
Check your answer
core. Team is @Inherited, so asking Sub for it finds the annotation on its superclass Base.