skip to content

What are the three @Retention policies in Java (SOURCE, CLASS, RUNTIME) and how does each affect when an annotation is available?

level: middleimportance: must knowfreq 70%

answer

  1. SOURCE -> CLASS -> RUNTIME = compiler-only / bytecode / reflection-visible
  2. CLASS is the silent default
  3. Frameworks need RUNTIME
  4. Reflection sees only RUNTIME
  5. @Override is SOURCE; @Audited for Spring is RUNTIME

basics

~20 s

@Retention says how long an annotation is kept. SOURCE: discarded after compiling. CLASS (the default): stored in the .class file but not loaded at runtime. RUNTIME: kept and readable via reflection while the program runs.

solid answer

~40 s

@Retention is a meta-annotation (an annotation placed on another annotation) that controls the lifetime of the annotated annotation, via a RetentionPolicy enum value. SOURCE means it exists only in source and is dropped by the compiler, used by compile-time tools like @Override or Lombok. CLASS, the default when you omit @Retention, keeps it in the .class bytecode but the JVM does not load it, so reflection cannot see it; only bytecode tools can. RUNTIME keeps it and the JVM loads it, so you can read it with reflection at runtime via getAnnotation. If you want a framework like Spring or JUnit to discover annotations by reflection, you must use RUNTIME. Too broad a policy keeps useless metadata around; too narrow makes reflection silently return nothing.

code

java · 9 lines
java
import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@interface Audited {}

class Service { @Audited void pay() {} }

// boolean seen = Service.class.getDeclaredMethod("pay")
//                       .isAnnotationPresent(Audited.class); // true (RUNTIME)

go deeper

for a junior

Knows the three names and that RUNTIME is the one frameworks read with reflection.

for a middle

Can explain each policy across the source->bytecode->runtime stages and that CLASS is the default.

for a senior

Reasons about why a custom annotation is silently invisible to reflection and picks the right policy for a given consumer (compiler vs bytecode tool vs framework).

for a principal

Weighs retention trade-offs at API-design scale — metadata footprint, tooling ecosystems (annotation processors vs bytecode weaving vs reflective scanning), and sets conventions for a codebase's custom annotations.

## What is an annotation? An **annotation** is a marker attached to code (a class, method, field, parameter, etc.), written with an `@` sign, e.g. `@Override`. It is *metadata*: information about the code that does nothing by itself. Some other party (the compiler, a framework, a tool) reads it and acts on it. ## What is a meta-annotation? A **meta-annotation** is an annotation placed on the *declaration of another annotation*. The standard ones live in `java.lang.annotation`: `@Retention`, `@Target`, `@Documented`, `@Inherited`, `@Repeatable`. They configure how the annotation you are defining behaves. `@Retention` is the most important one. ## What @Retention controls `@Retention` decides **how long the annotation survives** as your code moves through three stages: source code -> compiled `.class` bytecode -> running JVM. Its value is one of the `RetentionPolicy` enum constants. - **SOURCE**: The annotation exists only in the `.java` source. The compiler reads it (if it wants) and then *throws it away* — it never appears in the `.class` file. Use this for things only the compiler or source tools need: `@Override`, `@SuppressWarnings`, and Lombok annotations like `@Getter` (Lombok runs during compilation, generates code, then the annotation is gone). - **CLASS**: The annotation is written into the `.class` bytecode file, but when the JVM loads the class it *does not* keep the annotation in memory, so **reflection cannot see it**. This is the **default** — if you write a custom annotation and omit `@Retention`, you get CLASS. Tools that read bytecode directly (bytecode weavers, some analyzers) can still see it. - **RUNTIME**: The annotation is in the bytecode *and* the JVM loads it into the runtime class metadata, so it is **visible to reflection** while the program runs. This is what frameworks need: Spring, JUnit, Jackson, JPA all scan classes with reflection (`clazz.getAnnotation(...)`, `isAnnotationPresent(...)`), so their annotations are RUNTIME. ## How retention is checked At runtime you read annotations through the **Reflection API**: methods like `Class.getAnnotation(MyAnno.class)`, `Method.getAnnotations()`, `isAnnotationPresent(...)`. These return data **only for RUNTIME-retained** annotations. A SOURCE or CLASS annotation will make these methods return `null`/empty with no error — a classic silent bug. ## Choosing a policy Rule of thumb: if anything reads the annotation via reflection at runtime, use RUNTIME. If only the compiler/source tooling needs it, use SOURCE. CLASS is rarely chosen on purpose; you mostly get it by accident as the default. Picking too broad wastes a little space and keeps metadata you do not need; picking too narrow is worse because reflection quietly returns nothing. ## Worked example ```java import java.lang.annotation.*; @Retention(RetentionPolicy.RUNTIME) @interface Audited {} class Service { @Audited void pay() {} } // elsewhere: Method m = Service.class.getDeclaredMethod("pay"); boolean isAudited = m.isAnnotationPresent(Audited.class); // true ONLY because RUNTIME ``` If `@Audited` had no `@Retention` (defaulting to CLASS) or SOURCE, `isAnnotationPresent` would return `false`.

  • What is the default RetentionPolicy if you omit @Retention on a custom annotation?
    CLASS. The annotation is written to the .class file but not loaded by the JVM, so reflection cannot see it — a common cause of 'my framework ignores my annotation' bugs.
  • Why is @Override declared with SOURCE retention?
    Its only job is a compile-time check that a method actually overrides a supertype method. Nothing needs it after compilation, so keeping it in bytecode or at runtime would be wasted metadata.

Think of a sticky note on a document. SOURCE = note read during drafting then tossed. CLASS = note photocopied into the archived file but never pulled out again. RUNTIME = note kept on the live document so anyone working with it can read it anytime.

saying these in an interview costs you the question

  • Saying the default retention is RUNTIME (it is CLASS).
  • Claiming reflection can read CLASS- or SOURCE-retained annotations.
  • Thinking SOURCE annotations end up in the .class file.
  • Assuming retention affects what the annotation can be applied to (that is @Target).

context