Fundamentals 13 min read

Master Java Annotations: From Basics to Advanced Usage

This article explains what Java annotations are, how to define custom annotations, the built‑in meta‑annotations, annotation attributes, common built‑in annotations, and practical usage scenarios, providing code examples and runtime reflection details for developers.

JavaEdge
JavaEdge
JavaEdge
Master Java Annotations: From Basics to Advanced Usage

Introduction

Java annotations are a form of metadata that can be attached directly to source code. They describe relationships between program elements or between code and external resources (e.g., database tables). Annotations were introduced in JDK 5 to replace separate XML‑based metadata, improving maintainability and type safety.

Defining an Annotation

package com.guor.ClientNew;

public @interface MyAnnotation {
    // public static final property
    int age = 25;
    // abstract method defines an element
    String name();
}

An annotation is essentially an interface that implicitly extends java.lang.annotation.Annotation.

Meta‑annotations

@Retention

Specifies how long the annotation is retained. RetentionPolicy.SOURCE – present only in source code; discarded by the compiler. RetentionPolicy.CLASS – stored in the class file but not available at runtime. RetentionPolicy.RUNTIME – retained at runtime and accessible via reflection.

@Documented

Indicates that the annotation should be included in generated Javadoc.

@Target

Limits the kinds of program elements an annotation can be applied to. ElementType.ANNOTATION_TYPE – another annotation. ElementType.CONSTRUCTOR – a constructor. ElementType.FIELD – a field. ElementType.LOCAL_VARIABLE – a local variable. ElementType.METHOD – a method. ElementType.PACKAGE – a package. ElementType.PARAMETER – a method parameter. ElementType.TYPE – a class, interface, or enum.

@Inherited

If a superclass is annotated with an @Inherited annotation, subclasses inherit the annotation when they do not declare it themselves.

@Repeatable (Java 8)

Allows the same annotation to be applied multiple times to a single element. The repeatable annotation must specify a container annotation.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Persons {
    Person[] value();
}

@Repeatable(Persons.class)
public @interface Person {
    String role() default "";
}

@Person(role="CEO")
@Person(role="husband")
@Person(role="father")
@Person(role="son")
public class Man {
    String name = "";
}

public static void main(String[] args) {
    // Retrieve all annotations on Man
    Annotation[] anns = Man.class.getAnnotations();
    System.out.println("Total annotations: " + anns.length);
    // Cast to the container type
    Persons container = (Persons) anns[0];
    for (Person p : container.value()) {
        System.out.println(p.role());
    }
    // Safer check using isAnnotationPresent
    if (Man.class.isAnnotationPresent(Persons.class)) {
        Persons p2 = Man.class.getAnnotation(Persons.class);
        for (Person p : p2.value()) {
            System.out.println(p.role());
        }
    }
}

Annotation Elements (Attributes)

Elements are declared as parameter‑less methods. The method name becomes the element name and the return type defines the element type. Allowed types are primitive types, String, Class, enums, other annotations, and arrays of these.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
    int id();
    String msg();
}

@TestAnnotation(id = 3, msg = "hello annotation")
public class Test {}

Elements can have default values using the default keyword:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
    int id() default -1;
    String msg() default "default message";
}

If an annotation defines a single element named value, the element name may be omitted when using the annotation:

public @interface Check {
    String value();
}

@Check("hi")
int a; // equivalent to @Check(value="hi")

An annotation without any elements can be used without parentheses:

public @interface Perform {}

@Perform
public void testMethod() {}

Built‑in Annotations

@Override – signals that a method overrides a superclass method.

@Deprecated – marks a class or method as outdated; the compiler issues a warning when it is used.

@SuppressWarnings – tells the compiler to suppress specific warnings for the annotated element.

@SafeVarargs – suppresses unchecked warnings for var‑args of generic types.

@FunctionalInterface – enforces that the annotated interface has exactly one abstract method, enabling it to be used as a lambda target.

Example of a functional interface:

@FunctionalInterface
interface GreetingService {
    void sayMessage(String message);
}

GreetingService greet = msg -> System.out.println("Hello " + msg);

Typical Usage Scenarios

Annotations serve as metadata that can be processed:

At compile time – for code generation, documentation, or static analysis.

At runtime – via reflection, enabling frameworks (e.g., Spring, Hibernate) to configure behavior based on annotation values.

Annotations do not alter program logic directly; they provide information to tools and the JVM.

Runtime Representation

When an annotation is accessed at runtime, the JVM creates a dynamic proxy that implements the annotation interface. Calls to the annotation’s methods are delegated to AnnotationInvocationHandler, which retrieves values from an internal map populated from the class file’s constant pool.

Summary

Annotations are special interfaces that extend java.lang.annotation.Annotation and are used to attach metadata to program elements.

They are defined with @interface and can declare elements (attributes) of limited types.

Meta‑annotations such as @Retention, @Target, @Documented, @Inherited, and @Repeatable control the lifecycle, applicability, and repeatability of annotations.

Built‑in annotations ( @Override, @Deprecated, @SuppressWarnings, @SafeVarargs, @FunctionalInterface) aid compilation and framework integration.

Reflection can retrieve annotation data at runtime, but it incurs a performance cost, so use it judiciously.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaReflectionJDKannotationsMeta-Programming
JavaEdge
Written by

JavaEdge

First‑line development experience at multiple leading tech firms; now a software architect at a Shanghai state‑owned enterprise and founder of Programming Yanxuan. Nearly 300k followers online; expertise in distributed system design, AIGC application development, and quantitative finance investing.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.