Back to Blog
Java

Java Inherited Annotation: @Inherited Explained

java inherited annotation: Understand Java's @Inherited meta-annotation: its behavior, limitations, and how reflection resolves inherited annotations.

Java annotations@Inheritedmeta-annotationsreflectioncustom annotationsinheritance
A Java class hierarchy with an @Inherited annotation propagating from a base class to a subclass, shown as a diagram.

When you mark a custom annotation with @Inherited, you change how that annotation behaves in a class hierarchy. This is a common source of confusion because the rule is simple: @Inherited causes a class-level annotation to be inherited by subclasses, but only if the annotation is present on the superclass and the subclass does not declare it directly. The java inherited annotation mechanism is part of the java.lang.annotation package and only applies to annotations whose @Target includes ElementType.TYPE.

What @Inherited Does

The @Inherited meta-annotation is declared on an annotation type. When a class is annotated with that annotation, its subclasses automatically receive the same annotation unless the subclass explicitly declares it. This behavior is visible through reflection: getAnnotation() on a subclass returns the annotation if it was inherited from a superclass, while getDeclaredAnnotation() does not.

Consider this annotation:

import java.lang.annotation.*; @Inherited @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface Audited { String value() default "default"; }

If you place @Audited on a base class, every subclass inherits it. The inheritance is transitive: if B extends A and C extends B, and A has @Audited, then both B and C report @Audited via getAnnotation().

Declaring an Inherited Annotation

To make your own annotation inheritable, apply @Inherited to its declaration. The annotation must also have @Retention(RetentionPolicy.RUNTIME) if you intend to read it via reflection at runtime. @Inherited has no effect on annotations with CLASS or SOURCE retention because those are not available to the reflection API.

import java.lang.annotation.*; @Inherited @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface Version { int major(); int minor(); }

Now apply it to a superclass:

@Version(major = 1, minor = 0) public class BaseEntity { }

A subclass inherits the annotation automatically:

public class User extends BaseEntity { }

At runtime, User.class.getAnnotation(Version.class) returns the @Version instance with major=1, minor=0.

What @Inherited Propagates (and What It Doesn't)

@Inherited only propagates class-level annotations. It does not work for annotations on methods, fields, constructors, parameters, or local variables. If you place an @Inherited annotation on a method in a superclass, the override in a subclass will not inherit it. The annotation is not considered part of the method's contract.

Also, @Inherited does not work with interfaces. If an interface is annotated with an @Inherited annotation, a class implementing that interface does not inherit the annotation. The Java specification restricts inheritance to classes only. If you need annotation-like behavior across interfaces, you must query the interface separately or use a different design.

Another limitation: if the subclass itself declares the same annotation, the subclass's declaration shadows the inherited one. The subclass annotation is used, not the superclass's. This is consistent with the idea that an explicit declaration overrides inheritance.

How Reflection Resolves Inherited Annotations

The reflection API distinguishes between annotations present on the class itself and those inherited from a superclass. getAnnotation() walks up the class hierarchy and returns the first matching annotation. getDeclaredAnnotation() only checks the current class. This distinction is crucial when you need to know whether an annotation was explicitly declared or inherited.

public class AnnotationInspection { public static void main(String[] args) { System.out.println(User.class.getAnnotation(Version.class)); System.out.println(User.class.getDeclaredAnnotation(Version.class)); } }

For User above, getAnnotation() prints the annotation, while getDeclaredAnnotation() prints null. If you want to detect whether a subclass has explicitly overridden an annotation, use getDeclaredAnnotation().

The lookup is efficient because the JVM caches annotation metadata, but be aware that getAnnotation() may trigger class loading of superclasses. In most applications this is not a concern, but in extremely large hierarchies it can add overhead.

Practical Example: A Custom Inherited Annotation

A common use case is marking classes that need auditing or logging. Instead of repeating the annotation on every subclass, you can annotate the base class once and rely on inheritance.

@Inherited @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface Audited { String action() default "read"; } @Audited(action = "create") public class BaseRepository { } public class UserRepository extends BaseRepository { } public class AdminRepository extends BaseRepository { }

A framework that processes repositories can check for @Audited and apply the same behavior to all subclasses. The framework uses getAnnotation() to find the annotation, and it works because the annotation is inherited.

public void processRepository(Class<?> repositoryClass) { Audited audited = repositoryClass.getAnnotation(Audited.class); if (audited != null) { System.out.println("Audit action: " + audited.action()); } }

This pattern reduces duplication and keeps the metadata centralized. However, it also couples the subclass to the superclass's annotation. If you later remove the annotation from the base class, all subclasses silently lose it.

Common Pitfalls and Misconceptions

One frequent mistake is assuming that @Inherited applies to methods or interfaces. It does not. Another is forgetting to set @Retention(RUNTIME). Without runtime retention, reflection cannot see the annotation, so inheritance is irrelevant. Also, if the annotation's @Target does not include TYPE, the compiler rejects it on a class, and @Inherited has no meaning.

Another misconception is that @Inherited causes the annotation to be inherited from an interface. It does not. If you need to propagate annotation metadata through interfaces, you must handle that manually, for example by reading the annotation from the interface and merging it with the class's annotations.

Finally, @Inherited does not affect how getAnnotations() returns multiple annotations. If a superclass has multiple inherited annotations, they are all returned. The ordering is not specified, so rely on getAnnotation() for a specific type rather than iterating and filtering.

When to Use @Inherited ( and When Not To)

Use @Inherited when you want a class-level annotation to apply to all subclasses automatically. This is useful for framework-level metadata such as entity mappings, audit flags, or feature toggles that should be consistent across a hierarchy. It keeps the code DRY and avoids accidental omission.

Avoid @Inherited when the annotation is meant to be overridden per subclass and you need to detect the override. Even though you can use getDeclaredAnnotation() to detect an explicit declaration, the design is clearer if you force each subclass to declare the annotation. Also, do not use @Inherited for annotations on methods or fields; it will not work, and the annotation will be silently ignored for those targets.

For interfaces, there is no built-in inheritance mechanism. If you need to share annotation metadata across unrelated classes that implement a common interface, consider reading the annotation from the interface explicitly in your framework code. The interface can be annotated, and the implementation can be checked by walking the interface hierarchy manually.

The decision ultimately depends on whether the annotation represents an invariant of the class hierarchy or a per-class configuration. If it is an invariant, @Inherited reduces repetition. If it is a per-class choice, explicit annotation is safer and more maintainable because the annotation is visible directly on the class where it applies.

java inherited annotation: Practical Usage and Code Examples | RYUSLOG DEV