Basics of Annotation Processors

by: Ethan McCue

If you register an implementation of javax.annotation.processing.Processor with the ServiceLoader mechanism then you can make what is called an "Annotation Processor".

Annotations are just metadata that can be put onto classes, fields, etc.

You can declare your own annotation like this

package some.pack;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface YourAnnotation {
}

In this example the annotation can only be used on "types" like classes or interfaces and the metadata is only available on the source code.

So we can use that example annotation to mark a class of ours

@YourAnnotation
class SomeClass {}

And any annotation processors that the ServiceLoader can find at compile time can do stuff like generate brand new code, or add new compile time checks.

@SupportedAnnotationTypes("some.pack.MagicBean")
@SupportedSourceVersion(SourceVersion.RELEASE_17)
public final class AnnotationProcessor extends AbstractProcessor {
    @Override
    public boolean process(
            Set<? extends TypeElement> annotations,
            RoundEnvironment roundEnv
    ) {
        var filer = this.processingEnv.getFiler();
        var elements = roundEnv.getElementsAnnotatedWith(YourAnnotation.class);
        try {
            var file = filer.createSourceFile("brand.new.Code");
            try (var writer = file.openWriter()) {
                // there are libraries like javapoet which make doing this easier
                writer.append("""
                package brand.new;

                class Code {
                    public static final int NUMBER_OF_INSTANCES = %s;
                }
                """.formatted(elements.size()));
            }
            catch (IOException e) {
                throw new RuntimeException(e);
            } 
        }
        return true;
    }
}

Which in this case we generate a new class which has the number of annotated classes as a constant.


<- Index