When designing a custom annotation, how do @Retention and @Target shape whether and where it can be used, and what are the consequences of getting them wrong?
answer
- Retention: SOURCE/CLASS/RUNTIME; default CLASS
- only RUNTIME is reflection-visible
- Target = ElementType list; omit = almost anywhere
- TYPE_USE/TYPE_PARAMETER for type checkers
- forgot Retention => getAnnotation returns null
basics
~20 s@Retention says how long the annotation survives, SOURCE, CLASS, or RUNTIME. Only RUNTIME is readable by reflection. @Target restricts which code elements it can be placed on (method, field, type, etc.). Wrong choices mean your framework can't find it or it can't be applied where you need.
solid answer
~50 sTwo meta-annotations control a custom annotation's reach. @Retention(RetentionPolicy.X) sets its lifetime: SOURCE means the compiler discards it (only useful to source tools like Lombok or annotation processors); CLASS (the default) keeps it in the .class file but not for reflection; RUNTIME also makes it readable via reflection at runtime, which is what any runtime framework (Spring, JPA, validation) needs. If you forget @Retention, you get CLASS, and your reflective lookup returns nothing, a classic silent bug. @Target({ElementType...}) limits where the annotation may legally appear: TYPE, METHOD, FIELD, PARAMETER, CONSTRUCTOR, TYPE_USE, etc. With no @Target it can go almost anywhere; with a wrong/too-narrow @Target the compiler rejects valid placements. Get these wrong and either the annotation is invisible to your processing code or it can't be attached where the design intends, so they must match how the annotation will be consumed.
go deeper
Knows @Retention(RUNTIME) is needed for reflection and @Target limits where the annotation goes.
Explains all three retention policies, that the default is CLASS, and names common ElementType targets.
Maps retention/target choices to the consumer (runtime reflection vs compile-time processor), explains the silent null bug, and knows TYPE_USE/TYPE_PARAMETER and @Inherited/@Repeatable.
Designs the meta-annotation set deliberately for a framework's processing model, weighs SOURCE vs RUNTIME for artifact size/coupling, and reasons about inheritance, repeatability, and type-use semantics across module boundaries.
## The problem these solve Declaring `@interface Foo {}` isn't enough: you must also say **how long Foo survives** and **where it's allowed to appear**. Those are set by two **meta-annotations** (annotations placed on the annotation declaration): `@Retention` and `@Target`. ## `@Retention` — the lifetime `@Retention(RetentionPolicy.X)` chooses one of three policies, an ascending lifetime: | Policy | Kept in source? | Kept in .class file? | Visible via reflection at runtime? | Typical use | |---|---|---|---|---| | `SOURCE` | yes | **no** | no | source-only tools: Lombok, `@Override`-style compiler hints, annotation processors that run at compile time | | `CLASS` (**default**) | yes | yes | **no** | bytecode tools that read .class files but not reflection; rarely what you want for your own runtime logic | | `RUNTIME` | yes | yes | **yes** | any framework that inspects annotations at runtime via reflection (Spring, JPA, Jackson, Bean Validation) | Key consequence: **only `RUNTIME` is readable with reflection** (`method.getAnnotation(Foo.class)`). If you forget `@Retention` entirely, you get `CLASS`, so `getAnnotation` returns `null` and your framework silently does nothing, one of the most common custom-annotation bugs. ## `@Target` — where it may appear `@Target({ElementType...})` is an array of the program elements the annotation may be placed on. The main `ElementType` values: - `TYPE` — class, interface, enum, annotation, **record**. - `METHOD`, `CONSTRUCTOR`. - `FIELD`, `PARAMETER`, `LOCAL_VARIABLE`. - `ANNOTATION_TYPE` — only on other annotations (meta-annotations). - `PACKAGE`, `MODULE`. - `TYPE_PARAMETER` (Java 8+) — on a generic type variable `<T>`. - `TYPE_USE` (Java 8+) — on **any use of a type**, e.g. `@NonNull String`, `List<@NonNull T>`, casts, `new` expressions. This is what powers pluggable type checkers. - `RECORD_COMPONENT` (Java 16+) — on a record's components. **If you omit `@Target`, the annotation can be applied to almost any declaration.** Adding `@Target` *restricts* it: the compiler then **rejects** the annotation anywhere not in the list. So a too-narrow `@Target` blocks legitimate placements; a missing/too-broad one lets it leak onto elements your processing code never expects. ## How they interact with consumption Design these to match the **consumer**: - A runtime framework reading via reflection → `@Retention(RUNTIME)` is mandatory. - A compile-time annotation processor (JSR 269) → `SOURCE` is enough (and keeps the annotation out of the artifact). - An annotation that decorates fields for ORM mapping → `@Target(FIELD)` (or `{FIELD, METHOD}` to allow property access). ## Other relevant meta-annotations - `@Documented` — include the annotation in generated Javadoc. - `@Inherited` — a class-level annotation is inherited by subclasses (only for `TYPE`, and only via `getAnnotations`, not interfaces). - `@Repeatable(Container.class)` — allow the same annotation multiple times on one element (Java 8+), backed by an auto-generated container annotation. ## Worked example ```java import java.lang.annotation.*; @Retention(RetentionPolicy.RUNTIME) // readable by reflection @Target({ElementType.METHOD, ElementType.TYPE}) @Documented public @interface Audited { String action(); String[] roles() default {}; } ``` Consumed at runtime: ```java Audited a = m.getAnnotation(Audited.class); // null unless RUNTIME retention if (a != null) log(a.action(), a.roles()); ``` ## Pitfalls and consequences - **Forgot `@Retention`** → defaults to `CLASS` → reflection finds nothing → framework silently inert. - **Too-narrow `@Target`** → compile error when applied to a valid-by-intent site. - **No `@Target`** → annotation can land on unexpected elements; processing code may NPE or misbehave. - **Expecting `@Inherited` to cover interfaces or methods** → it only applies to class-level annotations inherited by subclasses.
- A framework's getAnnotation(Foo.class) keeps returning null even though @Foo is on the method. Why?@Foo almost certainly lacks @Retention(RUNTIME); the default CLASS policy keeps it out of reflection. Add @Retention(RetentionPolicy.RUNTIME).
- What's the difference between ElementType.TYPE and TYPE_USE?TYPE targets declarations of classes/interfaces/enums/records. TYPE_USE targets any *use* of a type, e.g. @NonNull String, generic arguments, casts, enabling pluggable type checkers.
saying these in an interview costs you the question
- Assuming the default retention is RUNTIME (it's CLASS)
- Expecting reflection to read a CLASS- or SOURCE-retained annotation
- Thinking omitting @Target means it can't be applied (it means almost anywhere)
- Believing @Inherited propagates to interfaces or to methods