Advanced Java August 30 ,2025

Annotations in Java (Part-2)

 This is a continuation of Part-1 (Annotations in Java).
In Part-1, we learned how annotations are defined and controlled using meta-annotations like @Retention, @Target, @Inherited, etc.

Now, in Part-2, we’ll focus on:

  • How annotations are processed (compile-time & runtime).
  • Built-in annotation use cases.
  • Framework applications (Spring, JUnit, Hibernate).
  • Advanced topics like annotation processors.
  • Best practices & limitations.
     

5. Processing Annotations

Annotations themselves don’t do anything until they are processed. Java provides mechanisms to process them either at compile-time (e.g., using Annotation Processors) or at runtime (e.g., using Reflection API).

Using Reflection API to Read Annotations at Runtime

The Reflection API allows us to access annotations dynamically while the program is running.

Example: 

import java.lang.annotation.*;
import java.lang.reflect.*;

// Step 1: Define custom annotation
@Retention(RetentionPolicy.RUNTIME) // must be RUNTIME for reflection
@Target(ElementType.METHOD)
@interface MyAnnotation {
    String value();
    int priority() default 1; // default value
}

// Step 2: Use annotation
class DemoClass {
    @MyAnnotation(value = "Task1", priority = 2)
    public void taskOne() {
        System.out.println("Executing Task One...");
    }

    @MyAnnotation("Task2") // priority will take default = 1
    public void taskTwo() {
        System.out.println("Executing Task Two...");
    }
}

// Step 3: Process annotation using Reflection
public class AnnotationProcessor {
    public static void main(String[] args) {
        try {
            Class cls = DemoClass.class;

            // Iterate through methods
            for (Method method : cls.getDeclaredMethods()) {
                // Check if method has @MyAnnotation
                if (method.isAnnotationPresent(MyAnnotation.class)) {
                    MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);

                    System.out.println("Method: " + method.getName());
                    System.out.println("  Value: " + annotation.value());
                    System.out.println("  Priority: " + annotation.priority());
                    System.out.println();
                }
            }

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

Output:

Method: taskOne
  Value: Task1
  Priority: 2

Method: taskTwo
  Value: Task2
  Priority: 1

Accessing Annotation Values Programmatically

As seen above, once we retrieve an annotation using getAnnotation(), we can call its methods to fetch values.

  • annotation.value() → gets "Task1" or "Task2".
  • annotation.priority() → gets 2 or default 1.
Compile-Time vs Runtime Processing
AspectCompile-Time ProcessingRuntime Processing
WhenDuring code compilationDuring program execution
HowUsing Annotation Processors (e.g., javax.annotation.processing.Processor)Using Reflection API
Use CasesCode generation, validation, dependency injection frameworks (e.g., Lombok, Dagger, MapStruct)Logging, runtime validation, frameworks like Spring, JUnit
PerformanceNo runtime cost (errors detected early)Slower (adds runtime overhead)
Retention Policy NeededSOURCE or CLASSRUNTIME

6. Annotation Use Cases in Java

Annotations in Java provide metadata that influences how the compiler, JVM, or frameworks behave. They are widely used in compile-time validation, runtime processing, and frameworks.

1. Compile-Time Instructions

Annotations that guide the Java compiler during code compilation.

Examples:

  • @Override
    Ensures that a method correctly overrides a method from its superclass. If not, the compiler throws an error.
class Parent {
    void display() {}
}

class Child extends Parent {
    @Override
    void display() {   // ✅ Valid override
        System.out.println("Child display");
    }
}
  • @SuppressWarnings
    Tells the compiler to suppress specific warnings (like unchecked, deprecation).
@SuppressWarnings("unchecked")
void useList() {
    java.util.List list = new java.util.ArrayList(); // No warning
    list.add("Hello");
}

Purpose:

  • Code safety check
  • Removes unnecessary compiler warnings

2. Runtime Processing

Annotations that can be read at runtime (with Reflection API) to modify behavior dynamically.

Examples:

  • @Entity (Hibernate/JPA)
    Marks a class as a database entity.
@Entity
class User {
    @Id
    private int id;
    private String name;
}
  • @Test (JUnit)
    Identifies a test method that should be executed by the test runner.
import org.junit.Test;

public class MyTest {
    @Test
    public void checkAddition() {
        assert(2 + 3 == 5);
    }
}

Purpose:

  • Read metadata at runtime
  • Change object behavior dynamically

3. Framework Usage

Most frameworks heavily rely on annotations for configuration and dependency injection.

a) Spring Framework

  • @Autowired → Injects dependency automatically.
@Component
class Service {
    void serve() { System.out.println("Service running..."); }
}

class Client {
    @Autowired
    Service service;
}
  • @Component → Marks a class as a Spring-managed bean.

b) JUnit (Testing)

  • @Test → Marks methods as test cases.
  • @Before → Runs before each test method.
  • @After → Runs after each test method.
import org.junit.*;

public class ExampleTest {
    @Before
    public void setup() { System.out.println("Setup before test"); }

    @Test
    public void testMethod() { System.out.println("Test running"); }

    @After
    public void cleanup() { System.out.println("Cleanup after test"); }
}

c) Hibernate / JPA

  • @Entity → Maps class to database table.
  • @Table → Specifies table name.
  • @Column → Maps field to DB column.
import jakarta.persistence.*;

@Entity
@Table(name="users")
class User {
    @Id
    @GeneratedValue
    private int id;

    @Column(name="username")
    private String name;
}

Purpose:

  • Eliminates boilerplate XML configuration
  • Simplifies dependency injection, testing, ORM

7. Advanced Topics in Java Annotations

1. Annotation Processing Tool (APT) in Java

  • Definition:
    The Annotation Processing Tool (APT) is a tool provided by Java (before Java 8, now replaced by javac built-in support) that allows developers to process annotations at compile time.
  • Purpose:
    • Generate additional code, XML files, or documentation.
    • Validate annotation usage (e.g., ensuring methods annotated with @MyCustomAnnotation follow specific rules).

Example: Lombok and MapStruct libraries internally use annotation processing to generate boilerplate code.

2. javax.annotation.processing package

  • This package provides APIs to write annotation processors.
  • Key Interfaces/Classes:
    • Processor: Interface for custom processors.
    • AbstractProcessor: Base class to simplify writing processors.
    • ProcessingEnvironment: Provides utilities like messager (logging), filer (for file creation), etc.
    • RoundEnvironment: Represents the current round of annotation processing.
    • SupportedAnnotationTypes: Defines annotations the processor supports.
    • SupportedSourceVersion: Defines Java version support.

3. Abstract Syntax Tree (AST) based annotation processing

  • AST Definition: The Abstract Syntax Tree is the internal tree representation of source code created by the compiler.
  • Annotation processors can traverse AST nodes and analyze code structure.
  • This is useful for:
    • Code generation (e.g., generating getters/setters).
    • Static analysis (e.g., checking correct usage of annotations).

Example: Lombok modifies the AST to inject code like @Getter, @Setter.

4. How annotation processors are registered (META-INF/services)

  • Annotation processors are discovered using the Service Provider Interface (SPI).
  • Steps:
    1. Create a file in META-INF/services/ directory.
    2. File name → javax.annotation.processing.Processor.
    3. Inside the file → fully qualified class name of your processor.

Example:

META-INF/services/javax.annotation.processing.Processor

File content:

com.example.MyAnnotationProcessor

This way, the Java compiler automatically detects your processor during compilation.

5. Example: Creating an Annotation Processor

Step 1: Define a custom annotation

import java.lang.annotation.*;

@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface MyAnnotation {
    String value();
}

Step 2: Create a processor

import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.*;
import java.util.Set;

@SupportedAnnotationTypes("MyAnnotation")
@SupportedSourceVersion(SourceVersion.RELEASE_11)
public class MyAnnotationProcessor extends AbstractProcessor {

    @Override
    public boolean process(Set annotations, RoundEnvironment roundEnv) {
        for (Element element : roundEnv.getElementsAnnotatedWith(MyAnnotation.class)) {
            String className = element.getSimpleName().toString();
            processingEnv.getMessager().printMessage(javax.tools.Diagnostic.Kind.NOTE,
                    "Found @MyAnnotation at " + className);
        }
        return true; // annotation claimed
    }
}

Step 3: Register processor

META-INF/services/javax.annotation.processing.Processor

com.example.MyAnnotationProcessor

Step 4: Usage Example

@MyAnnotation("Hello")
public class TestClass {
}

When you compile, the processor logs:

Note: Found @MyAnnotation at TestClass

8. Best Practices & Limitations of Annotations

Best Practices

  1. Use annotations for metadata, not business logic
    • Annotations should describe behavior/configuration (e.g., @Entity, @Autowired) instead of implementing core functionality.
  2. Prefer standard annotations over custom ones
    • Use built-in annotations (@Override, @Deprecated, @SuppressWarnings) before creating custom ones.
    • Helps with readability and consistency across projects.
  3. Use annotations for declarative configuration
    • Best suited for declarative tasks like dependency injection, validation, or mapping (@Column, @NotNull).
    • Avoid complex logic inside annotation processors.
  4. Balance annotations and external configuration
    • Small/medium projects: annotations improve simplicity and reduce XML/JSON config files.
    • Large projects: prefer configuration files for dynamic changes without recompilation.
  5. Document custom annotations properly
    • Always use @Documented so annotations appear in Javadoc.
    • Provide clear descriptions and usage examples.
  6. Follow naming conventions
    • Annotation names should be self-explanatory, e.g., @Transactional, @Cacheable, not vague names like @DoIt.
  7. Use retention policy wisely
    • RetentionPolicy.SOURCE → For compile-time checks only (e.g., Lombok).
    • RetentionPolicy.CLASS → For bytecode tools, not loaded at runtime.
    • RetentionPolicy.RUNTIME → For frameworks like Spring/Hibernate.

Limitations of Annotations

  1. Overuse reduces readability

    • Too many annotations clutter the code (annotation hell).
    • Example:
    @Entity
    @Table(name="users")
    @Getter
    @Setter
    @NoArgsConstructor
    @AllArgsConstructor
    public class User { ... }
    

    → Can confuse new developers.

  2. Hard-coded metadata
    1. Annotation values are fixed at compile-time.
    2. Unlike config files, you cannot change them without recompiling.
  3. Hidden complexity
    1. Over-reliance on annotations can hide actual behavior, making debugging harder.
    2. Example: Spring’s @Transactional manages transactions behind the scenes — not obvious to newcomers.
  4. Framework lock-in
    1. Heavy use of framework-specific annotations ties code to that framework.
    2. Migrating from Spring to another framework becomes difficult.
  5. Limited expressiveness
    1. Annotations only store constant values (primitives, enums, Strings, arrays).
    2. Cannot directly store objects or dynamic logic.
  6. Runtime overhead (for reflection)
    1. Annotations with RetentionPolicy.RUNTIME require reflection to read, which adds runtime overhead.
  7. Learning curve for beginners
    1. Custom annotations + processors may confuse new developers who are unfamiliar with meta-programming concepts.

Conclusion

Annotations in Java are a powerful feature that bridge the gap between code and metadata, making programs more readable, maintainable, and framework-friendly. Introduced in Java 5, they have evolved into an essential tool for modern Java development.

  • Predefined annotations like @Override, @Deprecated, and @SuppressWarnings simplify everyday coding tasks.
  • Meta-annotations provide control over how custom annotations behave and where they can be applied.
  • Custom annotations allow developers to define domain-specific metadata, while reflection and annotation processing make it possible to act on them dynamically at compile-time or runtime.
  • Frameworks like Spring, Hibernate, and JUnit heavily rely on annotations for declarative programming, dependency injection, ORM mapping, and testing.

That said, annotations should be used wisely. Overuse can lead to cluttered code, reduced readability, and framework lock-in. A balance between annotations and external configuration files ensures flexibility and maintainability, especially in large-scale projects.

In short:
Annotations make Java development simpler, cleaner, and more expressive — but they are most effective when applied judiciously, with clear documentation and an understanding of their impact on performance and maintainability.

Next Blog- Java I/O (NIO)

Sanjiv
0

You must logged in to post comments.

Related Blogs

Generics P...
Advanced Java August 08 ,2025

Generics Part- 2

Collection...
Advanced Java July 07 ,2025

Collections Framewor...

Mastering...
Advanced Java August 08 ,2025

Mastering Java Multi...

Annotation...
Advanced Java August 08 ,2025

Annotations

Java Memor...
Advanced Java August 08 ,2025

Java Memory Manageme...

Java Lambd...
Advanced Java August 08 ,2025

Java Lambda Expressi...

Java Funct...
Advanced Java August 08 ,2025

Java Functional Inte...

Java Strea...
Advanced Java August 08 ,2025

Java Stream API

JDBC (Java...
Advanced Java August 08 ,2025

JDBC (Java Database...

JDBC (Java...
Advanced Java September 09 ,2025

JDBC (Java Database...

Generics
Advanced Java August 08 ,2025

Generics

Java I/O (...
Advanced Java August 08 ,2025

Java I/O (NIO)

Introducti...
Advanced Java September 09 ,2025

Introduction to Desi...

Design Pat...
Advanced Java September 09 ,2025

Design Patterns in J...

Other Prin...
Advanced Java September 09 ,2025

Other Principles Beh...

Creational...
Advanced Java September 09 ,2025

Creational Design Pa...

In Creatio...
Advanced Java September 09 ,2025

In Creational Design...

In Creatio...
Advanced Java September 09 ,2025

In Creational Design...

Creational...
Advanced Java September 09 ,2025

Creational Design Pa...

Structural...
Advanced Java September 09 ,2025

Structural Design Pa...

In Creatio...
Advanced Java September 09 ,2025

In Creational Design...

Structural...
Advanced Java September 09 ,2025

Structural Design Pa...

Builder De...
Advanced Java September 09 ,2025

Builder Design Patte...

Structural...
Advanced Java September 09 ,2025

Structural Design Pa...

Structural...
Advanced Java September 09 ,2025

Structural Design Pa...

Structural...
Advanced Java September 09 ,2025

Structural Design Pa...

Structural...
Advanced Java September 09 ,2025

Structural Design Pa...

Structural...
Advanced Java September 09 ,2025

Structural Design Pa...

Structural...
Advanced Java September 09 ,2025

Structural Design Pa...

Design Pat...
Advanced Java September 09 ,2025

Design Patterns in J...

Chain of R...
Advanced Java September 09 ,2025

Chain of Responsibil...

Command De...
Advanced Java September 09 ,2025

Command Design Patte...

Interprete...
Advanced Java September 09 ,2025

Interpreter Design P...

Iterator D...
Advanced Java September 09 ,2025

Iterator Design Patt...

Mediator D...
Advanced Java September 09 ,2025

Mediator Design Patt...

Memento De...
Advanced Java September 09 ,2025

Memento Design Patte...

Observer D...
Advanced Java September 09 ,2025

Observer Design Patt...

State Desi...
Advanced Java September 09 ,2025

State Design Pattern...

Strategy D...
Advanced Java September 09 ,2025

Strategy Design Patt...

Template M...
Advanced Java September 09 ,2025

Template Method Desi...

Visitor De...
Advanced Java September 09 ,2025

Visitor Design Patte...

Prototype...
Advanced Java September 09 ,2025

Prototype Design Pat...

Java 8+ Fe...
Advanced Java October 10 ,2025

Java 8+ Features

SOLID Prin...
Advanced Java October 10 ,2025

SOLID Principles in...

Custom Imp...
Advanced Java October 10 ,2025

Custom Implementatio...

Custom Imp...
Advanced Java October 10 ,2025

Custom Implementatio...

Custom Imp...
Advanced Java October 10 ,2025

Custom Implementatio...

Custom Imp...
Advanced Java October 10 ,2025

Custom Implementatio...

How Iterat...
Advanced Java October 10 ,2025

How Iterators Work i...

How Concur...
Advanced Java October 10 ,2025

How ConcurrentHashMa...

Comparable...
Advanced Java October 10 ,2025

Comparable vs Compar...

Semaphore...
Advanced Java October 10 ,2025

Semaphore in Java

Get In Touch

G06, Kristal Olivine Bellandur near Bangalore Central Mall, Bangalore Karnataka, 560103

+91-8076082435

techiefreak87@gmail.com