Back to Blog
Java

Java @Target Annotation: Restricting Where Annotations Apply

Learn how Java's @Target meta-annotation restricts where custom annotations can be applied, with ElementType values, code examples, and common pitfalls.

Java annotationsmeta-annotationcustom annotationsElementTypereflection
Diagram showing Java @Target annotation restricting annotation placement to specific Java elements like methods and fields.

Java's @Target meta-annotation controls where a custom annotation can be applied. It is placed on an annotation type declaration and takes one or more ElementType values. Without it, a custom annotation is allowed in most declaration contexts, which often leads to misuse. Adding @Target restricts the annotation to specific element types, and the compiler enforces those restrictions at compile time.

What @Target Controls

@Target is a meta-annotation, meaning it annotates other annotations. It accepts an array of ElementType enum constants that define the kinds of Java elements the annotation can target. The compiler checks these restrictions when your annotation is used in source code, so an attempt to apply the annotation to an invalid element results in a compile error.

For example, if you declare an annotation with @Target(ElementType.METHOD), the compiler rejects its use on classes, fields, or parameters. This early validation prevents accidental misuse and makes the annotation's intended scope explicit.

Declaring a Custom Annotation with @Target

Here is a minimal custom annotation that is restricted to methods:

import java.lang.annotation.ElementType; import java.lang.annotation.Target; @Target(ElementType.METHOD) public @interface LogExecution { }

Now you can apply @LogExecution only to methods:

public class Service { @LogExecution public void process() { // method body } // @LogExecution on a field is a compile error private String name; }

The compiler rejects the field usage because @LogExecution targets only methods. This is the core behavior of @Target: it provides compile-time enforcement of an annotation's intended scope.

Common ElementType Choices and Their Effects

The ElementType enum includes constants for nearly every declaration or type-use context in Java. The table below lists the most frequently used ones and what they allow.

ElementTypeApplies toTypical Use Case
TYPEClasses, interfaces, enums, annotationsMarking a class as a DTO or entity
FIELDFields (instance and static)Injecting dependencies or validations
METHODMethod declarationsLogging, metrics, or transaction boundaries
PARAMETERConstructor and method parametersNull checks or parameter binding
CONSTRUCTORConstructor declarationsDependency injection in constructors
LOCAL_VARIABLELocal variables inside methodsDebugging or resource tracking
ANNOTATION_TYPEOther annotation declarationsBuilding meta-annotations
PACKAGEPackage declarations (package-info.java)Package-level configuration
TYPE_PARAMETERType parameter declarations (e.g., <T>)Type-level constraints
TYPE_USEAll type uses, including generics and castsType qualifiers like @NonNull

TYPE_USE is broader than the others because it applies to every place a type appears, including generic type arguments, array components, and type casts. For example, @NonNull String name uses TYPE_USE if @NonNull is declared with that target.

How @Target Affects Reflection and Runtime Behavior

@Target is enforced by the Java compiler. When you inspect annotations by reflection, you can read the @Target meta-annotation from the annotation type, but the reflection API does not re-run the compiler's target validation. For source code compiled by javac, an annotation declared with @Target(ElementType.METHOD) will not appear on fields because the compiler prevents that placement. Target restrictions are therefore a compile-time source-code guarantee rather than a runtime validation mechanism.

Consider this code:

import java.lang.annotation.*; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Audited { }

With this annotation, reflection code that checks a field with isAnnotationPresent(Audited.class) will see false, because the compiler never allowed @Audited to be placed on the field in the first place. The absence is enforced at compile time, not by the reflection API.

Common Mistakes and Pitfalls

One frequent mistake is forgetting to add @Target entirely. Without it, the annotation is allowed in most declaration contexts, which often leads to ambiguous code. For example, an annotation meant for methods might accidentally be placed on a class, and the compiler will not complain. Adding @Target immediately documents the intended scope.

Another mistake is using ElementType.TYPE when you actually want ElementType.TYPE_USE. TYPE allows the annotation on a class declaration, but not on a type use such as List<String> or a cast. If you need to annotate generic type arguments, you must include TYPE_USE in the target array.

Also note that @Target accepts an array, so you can allow multiple element types:

@Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) public @interface Timed { }

This is useful when the annotation has the same meaning across different declaration sites.

Combining @Target with Other Meta-Annotations

@Target works together with @Retention, @Documented, and @Inherited to define the full lifecycle of an annotation. @Retention controls whether the annotation is available in source, class files, or at runtime via reflection. @Documented ensures the annotation appears in Javadoc. @Inherited allows a subclass to inherit an annotation from its superclass, but only when the annotation is applied to a class and the target includes TYPE.

For a runtime-visible annotation that is restricted to methods and constructors, you would write:

@Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Tracked { }

The combination of @Target and @Retention is particularly important for frameworks that read annotations via reflection. If you forget RetentionPolicy.RUNTIME, the annotation will not be visible at runtime, even if @Target is correct.

Maintaining Compatibility and API Design

When you design a library or framework that exposes custom annotations, @Target becomes part of your public API. Narrowing the target set later is a breaking change because code that relied on the old restrictions may no longer compile. For example, if you initially allow @NotNull on fields and methods, then restrict it to only methods, any existing field usage breaks.

To minimize compatibility issues, start with a broad target set only if you are confident that the annotation is safe across those elements. Conversely, starting too narrow may force you to expand the set later, which is backward compatible but can dilute the annotation's semantic meaning.

Also consider how @Target interacts with TYPE_USE. If you plan to use the annotation in generic types or as a type qualifier, include TYPE_USE from the beginning. Adding it later is backward compatible, but omitting it may prevent users from applying the annotation in places they expect.

Edge Cases: TYPE_USE and Declaration Annotations

TYPE_USE is broader than TYPE when it comes to type references. @Target(TYPE) limits an annotation to class, interface, enum, and annotation declarations. @Target(TYPE_USE) permits the annotation wherever a type is used, including field types, method return types, generic type arguments, casts, and new expressions. For example:

@Target(ElementType.TYPE_USE) public @interface NonNull {} Box<@NonNull String> box;

Here @NonNull annotates the type argument String. TYPE alone would not allow that placement; TYPE_USE does. TYPE_USE is not the same as adding every declaration target, but it covers most places where a type appears in a declaration or expression.

When you read annotations via reflection, TYPE_USE annotations can be exposed through AnnotatedType objects rather than directly on AnnotatedElement instances. This distinction matters for libraries that inspect generic signatures or perform type-based processing.

Understanding these edge cases helps you choose the right target for your annotation and avoid surprising users who expect a certain placement to work.

Java @Target Annotation: Usage, ElementType Options, and Pitfalls | RYUSLOG DEV