Annotation Processing in Java with javax.annotation.processing API (APT)

Illustration for Annotation Processing in Java with javax.annotation.processing API (APT)
By Last updated:

A common misconception about Java annotations is that they are only useful at runtime when combined with reflection. Many developers don’t realize that annotations can also be processed at compile-time using the javax.annotation.processing API (APT), allowing tools and frameworks to generate code, enforce rules, and optimize programs before execution.

For example:

  • Lombok uses annotation processing to generate boilerplate code like getters and setters.
  • MapStruct generates mapper implementations at compile-time.
  • Dagger creates dependency injection code without reflection overhead.

Ignoring compile-time annotation processing often leads to runtime hacks with reflection that could have been avoided with safer, faster compile-time checks. Think of APT as having a project manager review your blueprints before construction, catching mistakes before they become costly errors.


Basics of Annotation Processing

Annotation processing is part of the JSR 269 API introduced in Java 6. It allows you to:

  1. Define custom annotations.
  2. Create processors that inspect these annotations during compilation.
  3. Generate code, resources, or validation errors.

Step 1: Define a Custom Annotation

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

@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface AutoToString {
}
  • RetentionPolicy.SOURCE ensures the annotation is only available at compile-time.
  • @Target(TYPE) restricts usage to classes.

Step 2: Create an Annotation Processor

import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
import java.util.Set;

@SupportedAnnotationTypes("AutoToString")
@SupportedSourceVersion(SourceVersion.RELEASE_11)
public class AutoToStringProcessor extends AbstractProcessor {

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        for (Element element : roundEnv.getElementsAnnotatedWith(AutoToString.class)) {
            processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE,
                "Processing annotation on: " + element.getSimpleName());
        }
        return true;
    }
}
  • Extends AbstractProcessor.
  • Uses RoundEnvironment to find elements annotated with @AutoToString.
  • Uses Messager to print compiler messages.

Step 3: Register the Processor

Create a file:

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

Content:

AutoToStringProcessor

This tells the compiler about your processor.


Step 4: Run the Processor

When compiling, the processor will run automatically. You’ll see messages like:

Note: Processing annotation on: MyClass

Advanced processors can generate source files using Filer API:

import javax.tools.JavaFileObject;
import java.io.Writer;

JavaFileObject file = processingEnv.getFiler().createSourceFile("GeneratedClass");
try (Writer writer = file.openWriter()) {
    writer.write("public class GeneratedClass { public String toString() { return "Hello"; } }");
}

Real-World Applications

  1. Lombok – Generates getters, setters, equals, hashCode, toString.
  2. MapStruct – Creates mapper implementations automatically.
  3. Dagger – Generates DI code at compile-time, avoiding reflection.
  4. Micronaut – Uses annotation processing for AOP and DI.

📌 What's New in Java Versions?

  • Java 5 – Introduced annotations.
  • Java 6 – Introduced JSR 269 annotation processing API.
  • Java 8 – Added type annotations and repeatable annotations.
  • Java 9 – Modules impacted service loader behavior for processors.
  • Java 11 – Improved compiler support for annotation processing.
  • Java 17 – No major changes, stable API.
  • Java 21 – No significant updates for APT.

Pitfalls and Best Practices

Pitfalls

  • Forgetting to register the processor in META-INF/services.
  • Using RetentionPolicy.RUNTIME instead of SOURCE for compile-time processors.
  • Overcomplicating processors, leading to long compile times.

Best Practices

  • Keep processors small and focused.
  • Use Filer API responsibly to avoid overwriting files.
  • Document generated code to aid debugging.
  • Combine with runtime reflection only when necessary.

Summary + Key Takeaways

  • Compile-time annotation processing prevents errors before runtime.
  • javax.annotation.processing API allows building custom processors.
  • Frameworks like Lombok, MapStruct, and Dagger rely heavily on APT.
  • Best practices: define correct retention, register processors properly, and generate code responsibly.

FAQ

  1. What is the difference between compile-time and runtime annotation processing?
    Compile-time uses APT (javax.annotation.processing) while runtime uses reflection.

  2. Can I use APT to generate new classes?
    Yes, using the Filer API.

  3. Do compile-time annotations exist at runtime?
    No, if marked with RetentionPolicy.SOURCE or CLASS.

  4. How do I debug annotation processors?
    Use Messager.printMessage() to output messages during compilation.

  5. Can annotation processing slow down compilation?
    Yes, if processors are complex or generate excessive code.

  6. What happens if multiple processors target the same annotation?
    All processors are executed; ordering is not guaranteed.

  7. Does Spring use compile-time annotation processing?
    No, Spring primarily relies on runtime reflection.

  8. How do I package and share an annotation processor?
    Distribute it as a JAR with META-INF/services configured.

  9. Can I use annotation processing in Gradle/Maven builds?
    Yes, processors are automatically picked up during compilation.

  10. Is APT still relevant with modern frameworks?
    Yes, frameworks like Lombok, MapStruct, and Micronaut rely heavily on it.