Page Content

Tutorials

How many Annotations are there in Java?

Annotations in Java

One type of syntactic metadata that can be included in Java code is called an annotation. This indicates that while annotations offer information about a program, they are not a part of it. Importantly, annotations don’t directly affect how the code they are attached to works. Rather, different tools can leverage this additional information in both the development and deployment stages.

There are several uses for annotations:

  1. Annotations are used by frameworks like as Spring and Spring-MVC to specify request routing or dependency injection locations.
  2. Annotations are used by code-generation tools like Lombok and JPA to produce Java (and SQL) code.
  3. The compiler, a deployment tool, or a code generator can process them.
  4. Compile-time inspection of annotated elements is another usage for them.

The @ symbol and the interface keyword are used to declare an annotation type (e.g., @interface MyAnno). An annotation’s members lack bodies but are specified similarly to methods. When the annotation is applied, these members behave similarly to fields. The java.lang.annotation.Annotation interface is automatically extended by all annotation types.

Several program constructs can use annotations:

  • Declarations: Classes, methods, fields, parameters, and enum constants are examples of declarations. It is even possible to annotate an annotation.
  • Type Uses (from JDK 8 onwards):  This enables the annotation of types in throws clauses, generic type parameters, method return types, and casts. Pluggable type systems, which increase code type checking, benefit greatly from type annotations.

Built-in Annotations

Java offers a number of pre-made annotations for typical programming tasks. Let’s examine a few of the most popular ones:

One marker annotation that is applicable to methods is @Override. Its main use is to signal that a method has to implement an abstract method from a superclass or interface, or override a method from a superclass.

The compiler will raise a compile-time error if a method marked with @Override does not, in fact, override or implement an inherited method. This helps avoid typical errors that could cause method overloading instead of the desired overriding, like typos in method names or wrong parameter lists.

@Override semantics has somewhat changed:

  1. Java 5: A non-abstract method declared in the superclass chain has to be overridden by the annotated method.
  2. Java 6 and later: This also holds true if the annotated method uses an abstract method that was defined in the interface hierarchy or superclass of the class.

Here’s an example demonstrating @Override with a class hierarchy:

// Shape.java
public abstract class Shape {
    public abstract Double area(); // Abstract method to be overridden 
}
// Circle.java
public class Circle extends Shape {
    private Double radius = 5.0;
    // The @Override annotation ensures this method correctly overrides the area() method from Shape 
    @Override
    public Double area() {
        return 3.14 * radius * radius;
    }
}
// Rectangle.java
public class Rectangle extends Shape {
    private Double width = 4.0;
    private Double height = 6.0;
    @Override
    public Double area() {
        return width * height;
    }
}
// Main.java
public class Main {
    public static void main(String[] args) {
        Shape circle = new Circle();
        Shape rectangle = new Rectangle();
        System.out.println("Area of Circle: " + circle.area());
        System.out.println("Area of Rectangle: " + rectangle.area());
    }
}

Code Output:

Area of Circle: 78.5
Area of Rectangle: 24.0

In this example, @Override explicitly declares that Circle.area() and Rectangle.area() are intended to override Shape.area(). If, for instance, Circle.area() was misspelled as areaX(), the compiler would flag an error because it wouldn’t be overriding any method in the Shape superclass.

Another marker annotation that shows a declaration is out-of-date and ought to be discontinued is @Deprecated. Classes, methods, fields, constructors, and interfaces can all use it.

An API may be deprecated for a number of reasons:

  • It’s possible that the API has bugs that are difficult to fix.
  • Using it is likely to result in mistakes.
  • Another API has taken its place.
  • It is experimental or out-of-date and susceptible to incompatible modifications.

Usually, the compiler will issue a warning when you utilise a deprecated program element. In order to make deprecated features easily recognised, Integrated Development Environments (IDEs) frequently highlight them. To let programmers know about the alternatives that are available, it is advised that the documentation contain @see or {@link} tags.

Here’s an example demonstrating @Deprecated:

// MyUtilityClass.java
public class MyUtilityClass {
    /**
     * @deprecated This method is obsolete; use {@link #newMethod()} instead.
     * It performs a simple calculation.
     */
    @Deprecated // Marks the method as deprecated 
    public int oldMethod(int a, int b) {
        return a + b;
    }
    public int newMethod(int a, int b) {
        return a * b;
    }
}
// Main.java
public class Main {
    /**
     * @deprecated This class is no longer recommended for use.
     * @see MyUtilityClass for current functionality.
     */
    @Deprecated // Marks the class as deprecated 
    public static class LegacyCalculator {
        public int add(int x, int y) {
            return x + y;
        }
    }
    public static void main(String[] args) {
        MyUtilityClass utility = new MyUtilityClass();
        int resultOld = utility.oldMethod(5, 3); // Compiler warning will be issued here for oldMethod
        int resultNew = utility.newMethod(5, 3);
        LegacyCalculator legacyCalc = new LegacyCalculator(); // Compiler warning for LegacyCalculator
        int legacyResult = legacyCalc.add(10, 20);
        System.out.println("Result from oldMethod: " + resultOld);
        System.out.println("Result from newMethod: " + resultNew);
        System.out.println("Result from LegacyCalculator: " + legacyResult);
    }
}

Code Output:

Result from oldMethod: 8
Result from newMethod: 15
Result from LegacyCalculator: 30

JDK 8 introduced the @FunctionalInterface annotation, which is intended exclusively for use on interfaces. It shows that the interface that has been annotated is functioning.

An interface that has just one abstract method is called a functional interface. Lambda expressions, which allow considering functionality as a method argument or code as data, make this kind of interface especially crucial.

The fact that @FunctionalInterface is only informational should not be overlooked. Even if an interface isn’t specifically annotated, it is by definition functional if it follows the criterion that there should be exactly one abstract method. However, if the interface does not follow the functional interface requirements (for example, if it contains many abstract methods), the compiler will report an error. For developers, this serves as a useful compile-time check.

Here’s an example illustrating @FunctionalInterface:

// MyFunctionalInterface.java
@FunctionalInterface // Indicates this interface is intended to be functional 
interface MyFunctionalInterface {
    void performAction(); // Single abstract method
}
// AnotherFunctionalInterface.java
@FunctionalInterface
interface AnotherFunctionalInterface {
    int calculate(int x, int y);
}
// InvalidFunctionalInterface.java (would cause a compile-time error if @FunctionalInterface was not commented out)
// @FunctionalInterface
interface InvalidFunctionalInterface {
    void method1();
    void method2(); // This would violate the single abstract method rule
}
// Main.java
public class Main {
    public static void main(String[] args) {
        // Using a lambda expression to implement MyFunctionalInterface 
        MyFunctionalInterface action = () -> System.out.println("Performing action!");
        action.performAction();
        // Using a lambda expression to implement AnotherFunctionalInterface
        AnotherFunctionalInterface calculator = (a, b) -> a + b;
        System.out.println("Result of calculation: " + calculator.calculate(10, 5));
    }
}

Code Output:

Performing action!
Result of calculation: 15

This illustrates how functional interfaces give lambda expressions a target, which reduces code length and improves readability.

Creating Custom Annotations

Java lets you add special metadata that is pertinent to your application or framework by allowing you to construct your own custom annotations in addition to the built-in ones.

To declare a custom annotation type, you use the @interface keyword:

// MyCustomAnnotation.java
public @interface MyCustomAnnotation {
    String author() default "Unknown"; // Element with a default value 
    int version(); // Required element (no default value)
    String[] tags() default {}; // Array element with a default empty array
}

Members of an annotation, such as author(), version(), and tags(), are declared as parameterless methods. One of the following types must be returned by them:

  1. A primitive type, such as String, Class, boolean, or int.
  2. A String or Class-type object.
  3. A type of enum.
  4. An additional kind of annotation.
  5. Any of the types mentioned above in an array. Neither generic nor throws clause-specific annotations are permitted.

As with invoking a constructor, when you apply a custom annotation, you supply values for its members in a parenthesised list. Only the members you want to modify need to have their default values specified.

Meta-Annotations

Special annotations called meta-annotations are applied to other kinds of annotations. They regulate the behaviour of your personalised annotation. These are the main meta-annotations:

  • @Target: This meta-annotation restricts the types of program elements to which an annotation can be applied. It takes one or more ElementType constants as arguments, specified within a braces-delimited list for multiple values. For example, ElementType.METHOD for methods, ElementType.FIELD for fields, or ElementType.TYPE for classes/interfaces/enums. JDK 8 introduced ElementType.TYPE_PARAMETER and ElementType.TYPE_USE for annotating type parameters and type uses, respectively.
  • @Retention: The retention policy, which establishes how long an annotation is kept during the compilation and deployment process, is defined in this meta-annotation. It accepts as an argument a RetentionPolicy enumeration constant:
    • RetentionPolicy.SOURCE: The annotation is eliminated during compilation and is only kept in the source file. beneficial for code generation or compile-time inspections.
    • RetentionPolicy.CLASS: Although the annotation is kept in the .class file, the JVM does not have access to it during runtime. If no policy is given, this is the default.
    • RetentionPolicy.RUNTIME: The annotation is accessible through the JVM at runtime and is kept in the .class file. This is necessary for annotations that require reflection-based querying.
  • @Documented: A marker meta-annotation that instructs Javadoc and other tools that the generated API documentation should use your annotation.
  • @Inherited: This marker meta-annotation is exclusive to class declaration annotations. If a subclass does not have its own annotation, it will inherit it from its superclass if it is annotated @Inherited. Note that annotations are not inherited from interfaces; they are only inherited from classes.
  • @Repeatable (JDK 8 onwards): With JDK 8 and later, the @Repeatable meta-annotation enables an annotation to be applied to the same element more than once. An annotation needs to be tagged with @Repeatable, which defines a container annotation type, in order to be repeatable. A value() function that returns an array of the repeating annotation type must be present in the container annotation.

Example: Custom Annotation and Reflection

Let’s create a custom annotation Todo and use reflection to retrieve its values at runtime.

import java.lang.annotation.*;
import java.lang.reflect.Method; // For runtime reflection 
// Define the custom annotation Todo
@Retention(RetentionPolicy.RUNTIME) // Make the annotation available at runtime 
@Target(ElementType.METHOD)      // Can only be applied to methods 
@Documented                      // Include in Javadoc 
@interface Todo {
    String assignee();          // Required element: who is responsible
    int priority() default 1;   // Optional element with a default value 
    String description() default "No description provided."; // Optional description
}
class TaskManager {
    @Todo(assignee = "John Doe", priority = 2, description = "Implement user authentication.")
    public void authenticateUser() {
        System.out.println("Authenticating user...");
    }
    @Todo(assignee = "Jane Smith") // Using default values for priority and description
    public void generateReport() {
        System.out.println("Generating report...");
    }
    @Todo(assignee = "John Doe", priority = 1, description = "Refactor old database queries.")
    @Todo(assignee = "Jane Smith", priority = 3) // Using @Repeatable implicitly or explicitly 
    public void databaseOperations() {
        System.out.println("Performing database operations...");
    }
}
public class AnnotationDemo {
    public static void main(String[] args) {
        TaskManager manager = new TaskManager();
        System.out.println("--- Inspecting TaskManager Methods ---");
        try {
            // Get the Class object for TaskManager 
            Class<?> cls = manager.getClass();
            // Iterate over all declared methods 
            for (Method method : cls.getDeclaredMethods()) {
                System.out.println("\nMethod: " + method.getName());
                // Check for single Todo annotation 
                if (method.isAnnotationPresent(Todo.class)) {
                    // Get single Todo annotation if present (pre-JDK 8 way for single annotations)
                    Todo todo = method.getAnnotation(Todo.class);
                    System.out.println("  Single Todo found:");
                    System.out.println("    Assignee: " + todo.assignee());
                    System.out.println("    Priority: " + todo.priority());
                    System.out.println("    Description: " + todo.description());
                }
                // Get all repeatable Todo annotations (JDK 8+ way for repeated annotations) 
                Todo[] todos = method.getAnnotationsByType(Todo.class);
                if (todos.length > 0) {
                    System.out.println("  Found " + todos.length + " Todo(s) (including repeatable):");
                    for (Todo t : todos) {
                        System.out.println("    Assignee: " + t.assignee() + ", Priority: " + t.priority() + ", Description: " + t.description());
                    }
                }
            }
        } catch (Exception e) {
            System.err.println("An error occurred: " + e.getMessage());
            e.printStackTrace();
        }
    }
}

Code Output:

--- Inspecting TaskManager Methods ---
Method: authenticateUser
  Single Todo found:
    Assignee: John Doe
    Priority: 2
    Description: Implement user authentication.
  Found 1 Todo(s) (including repeatable):
    Assignee: John Doe, Priority: 2, Description: Implement user authentication.
Method: generateReport
  Single Todo found:
    Assignee: Jane Smith
    Priority: 1
    Description: No description provided.
  Found 1 Todo(s) (including repeatable):
    Assignee: Jane Smith, Priority: 1, Description: No description provided.
Method: databaseOperations
  Found 2 Todo(s) (including repeatable):
    Assignee: John Doe, Priority: 1, Description: Refactor old database queries.
    Assignee: Jane Smith, Priority: 3, Description: No description provided.
Method: main

This example demonstrates how to define custom annotations with elements and default values and how to read this metadata at runtime using reflection. This enables tools or runtime processes to dynamically change their behaviour based on the annotations in the code without changing the program’s core logic.

Index