Java Annotations

Last update: 2021-01-10 11:11:46 +0800 +0800
Available translations: 日本語 / 中文(简体) / 中文(繁體)

An annotation is metadata attached to a declaration or type use. It is not a general-purpose object literal: an annotation type is a special interface whose members describe the permitted metadata.

Declaring Annotation Types #

Annotation members have no parameters or type parameters, and may not declare a throws clause. A member type is limited to a primitive, String, Class, an enum type, an annotation type, or a one-dimensional array of one of those types. Values must be compile-time constants, class literals, enum constants, annotations, or arrays thereof. null is never an annotation element value.

java
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Retry {
    int attempts() default 3;
    Class<? extends Throwable>[] retryOn() default {};
}

@Target constrains where an annotation may occur; relevant locations include declarations, type uses, type parameters, modules, and record components. If it is absent, the annotation may be written in any declaration context, but not in a type context. @Retention controls whether the compiler discards it (SOURCE), records it in the class file (CLASS, the default), or records it for reflection (RUNTIME). @Inherited affects only queries on Class, only for annotations on classes, and only along the superclass chain. It does not inherit annotations from interfaces, methods, fields, parameters, or type uses.

Defaults are compiled into the annotation type rather than copied into every use. Changing a default can therefore alter the result observed from an old client class file when it is run with the new annotation type.

Reflection and Access Boundaries #

Class, Method, Field, Constructor, Parameter, Package, and Module implement AnnotatedElement. The getDeclared* methods inspect annotations directly present on that element. The non-declared methods can include inherited class annotations; getAnnotationsByType and getDeclaredAnnotationsByType also unpack repeatable containers.

java
Retry[] policies = operation.getAnnotationsByType(Retry.class);

Runtime retention makes metadata visible; it does not grant access to a member or make private state safe to expose. Reflection must still pass Java language access checks and, for named modules, strong-encapsulation checks. trySetAccessible can only suppress applicable language checks when the package is opened to the caller; command-line --add-opens is a deployment decision, not a library contract. The legacy SecurityManager is deprecated for removal, so security design must use module boundaries, process permissions, input validation, and explicit APIs instead of assuming a global reflection policy.

Inspection alone should not be used as an initialization mechanism. Reflective construction and invocation can initialize a class, and annotation member access can require linked types to be resolved. Keep static initialization free of required configuration and externally visible side effects.

Repeatable Annotations #

@Repeatable names a container annotation whose sole required member is an array of the repeated annotation type. The container must have retention at least as long as the repeated type and a compatible target.

java
@Repeatable(Labels.class)
@interface Label { String value(); }

@interface Labels { Label[] value(); }

@Label("read") @Label("write")
final class Permission {}

For multiple occurrences, the language models the declaration as an implicitly declared container; class-file consumers therefore see the same container form as an explicit @Labels({ ... }) declaration. A single @Label is not generally byte-for-byte equivalent to a single-element explicit container. getAnnotation(Label.class) does not unpack a container, whereas getAnnotationsByType(Label.class) does.

Class Files and Proxy Instances #

Runtime-visible declaration annotations are stored in class-file attributes such as RuntimeVisibleAnnotations; parameter and type-use annotations have separate attributes. The compiler validates the annotation grammar and encodes element values. The VM verifies class-file structure, while core reflection parses the attributes on demand and exposes the result through AnnotatedElement.

The reflection implementation represents a returned runtime annotation as an object implementing the annotation interface, commonly a dynamic proxy in OpenJDK. Clients must treat that representation as unspecified. Rely on annotationType(), declared member methods, and the Annotation equality, hash-code, and string contracts rather than the proxy class or identity.

References #

Comments

Read or comment on GitHub (sign-in required to comment)