Skip to content
AITroveRead. Build. Understand.
Make this comfortable

Java annotations: retention and explicit processing

Last updated: 29 Sept 20263 min read
tutorial
IntermediateBy AITrove Editorial

An annotation attaches structured metadata to a declaration or type use; its effect depends on a compiler, tool, or runtime reader that interprets it.

Java 8+. Use a JDK that supports this release.

Metadata is not execution

A marker named Audited does not create an audit log. The program must read that marker and perform whatever action the application requires. The sample reads a service label, prints it, and stops; it deliberately contains no implicit interception machinery.

Retention controls how long the metadata survives. SOURCE metadata is unavailable after compilation. CLASS metadata is stored in class files but is not exposed as a runtime annotation through ordinary reflection. RUNTIME lets this program read the annotation while it runs.

Target limits legal placements. A type-level service marker should not accidentally be placed on a local variable and then silently ignored by a reader that only checks classes.

Define missing metadata behavior

getAnnotation may return null. Decide whether absence is allowed, defaults to another policy, or rejects registration. The sample uses an explicit unlabelled state rather than dereferencing a missing annotation.

Annotation values describe metadata; they cannot carry arbitrary mutable service objects. Keep operational state outside the annotation. If a value names a handler class, instantiating and validating that handler is a separate step.

Annotation processing during compilation and reflection at runtime are different mechanisms. A compile-time generator can create source files without the runtime retaining its marker. This lesson uses the runtime case only.

Separate runtime inspection from compile-time processing

Runtime retention makes a marker available to reflective consumers. Source retention serves a compiler-time consumer and does not keep the annotation available for later reflection. An annotation does no work merely by existing; the consumer owns validation, generated output or execution behavior. Compile-time processing is a separate build operation that needs a declared processor and a test for accepted and rejected declarations.

Working program

Java
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

public class ServiceLabels {
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    @interface ServiceLabel { String value(); }
    @ServiceLabel("shipment-index")
    static final class ShipmentIndex { }
    public static void main(String[] args) {
        ServiceLabel label = ShipmentIndex.class.getAnnotation(ServiceLabel.class);
        System.out.println(label == null ? "unlabelled" : label.value());
    }
}

Output

Output
shipment-index

Cost and design choices

One annotation lookup does not scan every object instance. Processing a registry of n service classes still requires work for those n classes and any generated registrations. Cache a validated registry rather than repeating inspection per request.

The marker does not establish authorization, a transaction, or a thread-safety guarantee. Such behavior requires tested code that owns those operations.

Common Mistakes

  • Do not expect a marker to execute behavior by itself.
  • Do not use CLASS retention when the reader requires runtime reflection.
  • Do not assume annotation inheritance applies to every placement.

Connect the contracts

An annotation records metadata; compare it with the behavior enforced by interface contracts.

Apply this contract in Spring

Spring MVC request validation: reject invalid commands before mutation, Spring transactional methods: call paths and rollback assumptions. These lessons keep framework assembly separate from the Java contract.

java
annotations
Storage details