skip to content

What is the difference between getAnnotations() and getDeclaredAnnotations(), and when does @Inherited matter?

level: seniorimportance: should knowfreq 48%

answer

  1. declared = direct only
  2. getAnnotations = direct + inherited
  3. @Inherited: classes only, superclass only
  4. no inheritance through interfaces or overridden methods
  5. without @Inherited the two are identical
  6. frameworks add their own meta-annotation scanning

basics

~10 s

getDeclaredAnnotations() returns only annotations written directly on that element. getAnnotations() also includes inherited ones from a superclass — but only annotations themselves marked @Inherited, and only for class inheritance.

solid answer

~40 s

On an AnnotatedElement, getDeclaredAnnotations() returns exactly the annotations present directly on that element. getAnnotations() returns those plus any inherited ones. Inheritance only applies to classes (not methods, fields, or interfaces) and only for annotation types meta-annotated with @Inherited: such an annotation on a superclass shows up via getAnnotations() on a subclass that doesn't redeclare it. Without @Inherited, getAnnotations() and getDeclaredAnnotations() return the same set. The same split exists for the singular accessors: getAnnotation() walks the inheritance chain; getDeclaredAnnotation() does not. A frequent gotcha is expecting @Inherited to flow through interfaces or onto overridden methods — it does neither. Frameworks like Spring don't rely on @Inherited; they implement their own meta-annotation and interface scanning (AnnotatedElementUtils) because JDK inheritance is too limited.

code

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

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

@Marker class Base {}
class Child extends Base {}

public class Demo {
    public static void main(String[] args) {
        System.out.println(Child.class.getAnnotation(Marker.class));          // @Marker() (inherited)
        System.out.println(Child.class.getDeclaredAnnotation(Marker.class));  // null (not declared here)
    }
}

go deeper

for a junior

Knows there are 'declared' and regular annotation getters and that one can include inherited ones.

for a middle

Correctly states getDeclaredAnnotations is direct-only while getAnnotations adds inherited ones, and that @Inherited enables that.

for a senior

Articulates the precise limits of @Inherited (classes only, superclass only, not interfaces/methods) and why getAnnotations==getDeclaredAnnotations without it; handles repeatables.

for a principal

Explains why frameworks build their own annotation search (meta-annotations, interface scanning, attribute merging) over the JDK's narrow model, and weighs reflection cost vs caching when scanning large class hierarchies.

## The AnnotatedElement contract Anything you can put an annotation on — a `Class`, `Method`, `Field`, `Constructor`, `Parameter`, `Package` — implements the `AnnotatedElement` interface, which defines the annotation-reading methods. Two pairs matter here: - `getAnnotations()` / `getAnnotation(Class)` — include **inherited** annotations. - `getDeclaredAnnotations()` / `getDeclaredAnnotation(Class)` — only annotations **declared directly** on this element, ignoring inheritance. There is also `isAnnotationPresent(Class)`, which (like `getAnnotation`) respects inheritance. ## What 'inherited' means here Normally annotations are **not** inherited at all: if a superclass has `@Foo`, a subclass does not automatically have `@Foo`. You can change that for a *specific annotation type* by meta-annotating its definition with `@Inherited`: ```java @Inherited @Retention(RetentionPolicy.RUNTIME) @interface Component {} ``` Now if `Base` is `@Component` and `class Child extends Base` does not redeclare it, then `Child.class.getAnnotation(Component.class)` returns the `@Component` instance (found by walking up the superclass chain), while `Child.class.getDeclaredAnnotation(Component.class)` returns `null` (it isn't declared on `Child` itself). ## The strict limits of @Inherited `@Inherited` is far more restricted than people expect: 1. **Classes only.** It applies only to class-to-class inheritance. Annotations on methods, fields, or constructors are never inherited, even with `@Inherited` — so an overriding method does **not** inherit annotations from the method it overrides. 2. **Superclasses, not interfaces.** An `@Inherited` annotation on an *interface* does **not** propagate to classes that implement it. Only the `extends` chain is walked. 3. **No effect without @Inherited.** If the annotation type is not meta-annotated `@Inherited`, then `getAnnotations()` and `getDeclaredAnnotations()` return the identical set — inheritance simply doesn't happen. ## getAnnotations vs getDeclaredAnnotations in summary | | declared on element | inherited from superclass (if @Inherited) | |---|---|---| | `getDeclaredAnnotations()` | yes | no | | `getAnnotations()` | yes | yes | For an element with no inheritance in play (a method, or a class whose annotations aren't `@Inherited`), the two are equivalent. ## Why frameworks roll their own Real-world needs — meta-annotations (an annotation annotated with another annotation, like Spring's `@RestController` being `@Controller` + `@ResponseBody`), annotations declared on interfaces, annotations on overridden methods — are **not** covered by JDK `@Inherited`. So Spring provides `AnnotatedElementUtils`/`AnnotationUtils` that do recursive, interface-aware, meta-annotation-aware searching. Understanding the JDK's narrow guarantees explains *why* those utilities exist. ## Order and identity Both methods return a fresh array each call (you may safely modify it), and `@Repeatable` annotations are surfaced as their container unless you use `getAnnotationsByType`/`getDeclaredAnnotationsByType`, which unwrap repeatables.

  • Does an @Inherited annotation on an interface propagate to an implementing class?
    No. @Inherited only follows the superclass (extends) chain, never interfaces. An annotation on an interface is never inherited by implementors via the JDK.
  • How do you read @Repeatable annotations correctly via reflection?
    Use getAnnotationsByType(X.class) / getDeclaredAnnotationsByType(X.class); plain getAnnotation returns null because the repeated annotations are wrapped in a generated container annotation.

saying these in an interview costs you the question

  • Thinking @Inherited works for methods or fields
  • Expecting @Inherited to flow from an interface to its implementors
  • Assuming getAnnotations() always returns more than getDeclaredAnnotations()
  • Believing meta-annotations are resolved by the JDK's getAnnotations()

context