Understanding Java Annotation Retention Policies: SOURCE vs CLASS vs RUNTIME

Illustration for Understanding Java Annotation Retention Policies: SOURCE vs CLASS vs RUNTIME
By Last updated:

One of the most common mistakes developers make when working with Java annotations is choosing the wrong retention policy. For example, a developer may define an annotation without specifying its retention, assuming it will be available at runtime. Later, when using reflection to read the annotation, it mysteriously returns null.

This happens because annotations default to RetentionPolicy.CLASS, meaning they’re stored in the bytecode but discarded at runtime. To use them with frameworks like Spring, Hibernate, or JUnit, developers need RetentionPolicy.RUNTIME.

Retention policies determine how long an annotation is kept—like deciding whether sticky notes on your desk should be thrown away immediately (SOURCE), kept in your drawer (CLASS), or pinned permanently on your wall (RUNTIME).


Retention Policies Explained

1. RetentionPolicy.SOURCE

  • Definition: Annotations are discarded by the compiler and never stored in .class files.
  • Use Case: Compile-time checks, documentation, code generation.
  • Examples: @Override, @SuppressWarnings.
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.SOURCE)
@interface Marker { }

public class Demo {
    @Marker
    public void test() {}
}
  • The annotation exists in source code only. Reflection APIs cannot access it.

2. RetentionPolicy.CLASS

  • Definition: Annotations are stored in the .class file but discarded by the JVM at runtime.
  • Use Case: Bytecode analysis, tools like Lombok or compilers that inspect .class files.
  • Examples: Some compiler-level annotations.
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.CLASS)
@interface Marker { }

public class Demo {
    @Marker
    public void test() {}
}
  • Available in bytecode but not accessible via reflection.

3. RetentionPolicy.RUNTIME

  • Definition: Annotations are retained in .class files and available via reflection at runtime.
  • Use Case: Frameworks that depend on annotations for behavior.
  • Examples: @Entity (Hibernate), @Test (JUnit), @Autowired (Spring).
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Method;

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

public class Demo {
    @Marker
    public void test() {}
    public static void main(String[] args) throws Exception {
        Method method = Demo.class.getMethod("test");
        System.out.println(method.isAnnotationPresent(Marker.class)); // true
    }
}
  • Available via reflection at runtime.

📌 What's New in Java Versions?

  • Java 5 – Introduced annotations and retention policies.
  • Java 8 – Added type annotations and repeatable annotations.
  • Java 9 – Strong module encapsulation slightly affected reflection access.
  • Java 11 – No significant updates for retention policies.
  • Java 17 – Stronger runtime encapsulation but policies remain unchanged.
  • Java 21 – No significant updates for retention policies.

Pitfalls and Best Practices

Pitfalls

  • Forgetting @Retention leads to default CLASS, which is not visible at runtime.
  • Using RUNTIME unnecessarily can bloat metadata and slow startup.
  • Mixing retention policies incorrectly (e.g., expecting SOURCE annotations at runtime).

Best Practices

  • Use SOURCE for compile-time checks and code generation.
  • Use CLASS for tools analyzing bytecode.
  • Use RUNTIME only when you need reflection (Spring, Hibernate, testing frameworks).
  • Always explicitly declare @Retention to avoid confusion.

Summary + Key Takeaways

  • Retention policies define how long annotations are preserved.
  • SOURCE: available only in source code.
  • CLASS: stored in bytecode, not accessible at runtime.
  • RUNTIME: preserved at runtime for reflection.
  • Frameworks like Spring, Hibernate, JUnit rely on RUNTIME annotations.

FAQ

  1. What is the default retention policy in Java?
    CLASS is the default if @Retention is not specified.

  2. Why can’t I access my annotation via reflection?
    Likely because it has SOURCE or CLASS retention instead of RUNTIME.

  3. When should I use SOURCE retention?
    For annotations used by compilers or tools like Lombok that work at compile-time.

  4. Can I change an annotation’s retention at runtime?
    No, it’s fixed when the annotation is defined.

  5. Do runtime annotations affect performance?
    Slightly, since metadata is loaded into memory, but typically negligible.

  6. What’s an example of CLASS retention in practice?
    Compiler-level annotations stored in bytecode but not needed at runtime.

  7. Can I combine retention policies?
    No, only one retention policy can be assigned per annotation.

  8. Does reflection work with CLASS retention?
    No, reflection only works with RUNTIME retention.

  9. How does Spring use retention policies?
    All Spring annotations (@Autowired, @Component, etc.) use RUNTIME retention.

  10. Should I always declare retention explicitly?
    Yes, it avoids confusion and makes intent clear.