Java › Annotations

How does Java's annotation processing API (javax.annotation.processing) enable compile-time code generation frameworks like Lombok or Dagger, and what are its key limitations?

Advanced

Answer

The Processor SPI lets a library register a class implementing AbstractProcessor, invoked automatically by javac during compilation for any annotations it declares support for via @SupportedAnnotationTypes -- it can generate entirely new source files (via the Filer API) that then get compiled alongside the original code in the same compilation round, but critically, standard annotation processors cannot modify existing source files (only generate new ones), which is why Lombok's field/method injection into existing classes actually relies on a lower-level, officially unsupported compiler API hack rather than the standard processor mechanism.
@SupportedAnnotationTypes("com.example.AutoValue")
@SupportedSourceVersion(SourceVersion.RELEASE_17)
public class AutoValueProcessor extends AbstractProcessor {
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        for (Element element : roundEnv.getElementsAnnotatedWith(AutoValue.class)) {
            // generates a NEW source file implementing the value class, via processingEnv.getFiler()
        }
        return true;
    }
}
Real-world example Google's AutoValue library uses the standard annotation processing API to generate a complete new immutable implementation class alongside an abstract class marked @AutoValue, whereas Lombok (needing to inject code into the existing class itself rather than generate a new file) has to rely on unofficial compiler internals, explaining why Lombok has historically had more compiler-version compatibility issues than AutoValue.

Common follow-ups: Why can't a standard annotation processor modify the source file it's processing?;What alternative approaches exist for frameworks that need to modify existing classes, like AspectJ's compile-time weaving?