Understanding Annotations in Java

Annotations in Java, introduced in JDK 1.5, serve as a form of metadata, providing declarative information about code elements. Unlike comments, which are for human readers, annotations are intended for the Java compiler or runtime environment.

Annotations can be applied to packages, classes, fields, methods, and parameters, offering insights or instructions to tools and the JVM.

Annotation Use Cases

Annotations facilitate several key functionalities:

  1. Documentation Generation: Annotations like @author, @version, and @since can be used with tools like javadoc to generate API documentation. For example:

    /**
     * Demonstrates Javadoc annotation for documentation.
     */
    public class DocumentGenerationDemo {
        /**
         * Calculates the sum of two integers.
         * @param a An integer.
         * @param b An integer.
         * @return The sum of a and b.
         */
        public int add(int a, int b) {
            return a + b;
        }
    }
    

    Running javadoc DocumentGenerationDemo.java will generate HTML documentation based on these annotations.

  2. Code Analysis: Through reflection, annotations can be read at runtime to analyze code, enabling frameworks and tools to adapt behavior based on annotated elements.

  3. Compile-Time Checks: Annotations like @Override ensure that a method correctly overrides a superclass or interface method, catching potential errors during compilation.

Predefined JDK Annotations

The JDK provides several built-in annotations:

  • @Override: Verifies that a method overrides a method from a superclass or interface.
  • @Deprecated: Marks an element (class, method, etc.) as obsolete, indicating it should no longer be used.
  • @SuppressWarnings: Suppresses compiler warnings. It can take arguments like "all" to suppress all warnings.
@SuppressWarnings("all")
public class PredefinedAnnotationDemo {

    @Override
    public String toString() {
        return super.toString();
    }

    @Deprecated
    public void displayDeprecatedMethod() {
        // This method is now obsolete.
    }

    public void testMethod() {
        displayDeprecatedMethod(); // In IDEs, this might show a strike-through.
    }
}

Custom Annotations

Custom annotations are defined using the @interface keyword. The structure of a custom annotation resembles an interface definition:

public @interface MyAnnotation {
    // attribute declarations
    String value(); // A common attribute name, often used as a shorthand
    int count() default 1; // Attribute with a default value
    String[] tags(); // Array attribute
}

The attributes within an annotation are essentially abstract methods. Their return types are restricted to:

  • Primitive types
  • String
  • Enums
  • Other annotations
  • Arrays of any of the above types

If a attribute has a default value using the default keyword, it's optional when using the annotation. If an annotation has only one attribute named value, the attribute name can be omitted when applying the annotation.

For example, applying @MyAnnotation:

@MyAnnotation(value = "example", count = 5, tags = {"tag1", "tag2"})
public class AnnotatedClass { ... }

// If only 'value' needs to be set:
@MyAnnotation("singleValue")
public class AnotherClass { ... }

Meta-Annotations

Meta-annotations are annotations that annotate other annotations, defining their behavior and usage.

  • @Target: Specifies where an annotation can be applied. ElementType enum provides options like TYPE (class, interface, enum), METHOD, FIELD, PARAMETER, LOCAL_VARIABLE, CONSTRUCTOR, PACKAGE, etc.

    import java.lang.annotation.ElementType;
    import java.lang.annotation.Target;
    
    @Target({ElementType.TYPE, ElementType.METHOD})
    public @interface MyTargetAnnotation {
        String description();
    }
    
  • @Retention: Defines how long the annotation should be retained. RetentionPolicy enum specifies the retention scope:

    • SOURCE: Discarded by the compiler.
    • CLASS: Recorded in the .class file but not available at runtime (default).
    • RUNTIME: Recorded in the .class file and available via reflection at runtime.
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyRuntimeAnnotation {
        String message();
    }
    
  • @Documented: Indicates that the annotation should be included in generated API documentation.

  • @Inherited: Specifies that an annotation applied to a class should be inherited by its subclasses.

Annotation Practical Example

This example demonstrates a simple framework that uses annotations to trigger method execution. It defines a Pro annotation to specify a class and method name, and then uses reflection to instantiate the class and invoke the method.

Pro.java:

package com.example.annotations;

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

/**
 * Annotation to specify the class and method to be invoked.
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Pro {
    String className();
    String methodName();
}

Demo1.java:

package com.example.annotations;

public class Demo1 {
    public void execute() {
        System.out.println("Executing Demo1's execute method...");
    }
}

ReflectTest.java:

package com.example.annotations;

import java.lang.reflect.Method;

@Pro(className = "com.example.annotations.Demo1", methodName = "execute")
public class ReflectTest {
    public static void main(String[] args) {
        try {
            // Get the annotation from this class
            Pro annotation = ReflectTest.class.getAnnotation(Pro.class);

            // Retrieve class and method names from the annotation
            String className = annotation.className();
            String methodName = annotation.methodName();

            // Load the class
            Class<?> targetClass = Class.forName(className);

            // Create an instance of the class
            Object instance = targetClass.getDeclaredConstructor().newInstance();

            // Get the method object
            Method method = targetClass.getMethod(methodName);

            // Invoke the method
            method.invoke(instance);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Another example demonstrates a simple test framework that runs methods annotated with @Check and logs any exceptions.

Calculator.java:

package com.example.testing;

public class Calculator {

    @Check
    public void add() {
        System.out.println("1 + 0 = " + (1 + 0));
    }

    @Check
    public void subtract() {
        System.out.println("1 - 0 = " + (1 - 0));
    }

    @Check
    public void multiply() {
        System.out.println("1 * 0 = " + (1 * 0));
    }

    @Check
    public void divide() {
        // This will cause an ArithmeticException
        System.out.println("1 / 0 = " + (1 / 0));
    }

    public void status() {
        System.out.println("Calculator is running...");
    }
}

Check.java:

package com.example.testing;

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

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Check {
}

TestCheck.java:

package com.example.testing;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Method;

public class TestCheck {
    public static void main(String[] args) {
        Calculator calculator = new Calculator();
        Class<?> calcClass = calculator.getClass();
        Method[] methods = calcClass.getMethods();
        int errors = 0;

        try (BufferedWriter writer = new BufferedWriter(new FileWriter("bug_report.txt"))) {
            for (Method method : methods) {
                if (method.isAnnotationPresent(Check.class)) {
                    try {
                        method.invoke(calculator);
                    } catch (Exception e) {
                        errors++;
                        writer.write(method.getName() + " method failed.");
                        writer.newLine();
                        writer.write("Error Type: " + e.getCause().getClass().getSimpleName());
                        writer.newLine();
                        writer.write("Message: " + e.getCause().getMessage());
                        writer.newLine();
                        writer.write("--------------------------");
                        writer.newLine();
                    }
                }
            }
            writer.write("Total errors found: " + errors);
            writer.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Tags: java annotations reflection metadata JDK

Posted on Fri, 11 Sep 2026 16:04:07 +0000 by Jakehh