Close

Java Pluggable Annotation Processor

[Last Updated: Dec 20, 2018]

Java 6 introduced Annotation processing API. This is compile time processing rather than runtime. During runtime we can still use reflections to analyze the annotation and customize the behavior. Compile time annotation processing is entirely different concept. With javac process, we can plug in our custom annotation 'processor' (a Java class) and can do the following things.

  • Based on the information provided by the annotations in code, we can do some validations and issue custom errors/warnings. Errors will cause compilation to fail. That means we can add more compile time type safety. We can also enforce some design rules.
  • Generate new code and create .java files, which will also get compiled by the already running compiler. In fact we can create any type of file e.g. properties/resources etc.

Writing the Processor

  1. Extend the class javax.annotation.processing.AbstractProcessor (implements Processor)
  2. Override process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv). This is the only abstract method we have to override. We put all our processing logic in this method (the processing API will be explained later).
  3. Put annotation @SupportedAnnotationTypes(...) on the class level to specify what annotations we want to register to. Or override getSupportedAnnotationTypes and return Set<String> with the same.
  4. Put annotation @SupportedSourceVersion to specify what java version we are targeting. Or override getSupportedSourceVersion and return the same.
    @SupportedSourceVersion(SourceVersion.RELEASE_8)
    @SupportedAnnotationTypes({com.example.MyAnnotation.class})
    public class MyAnnotationProcessor extends AbstractProcessor {
    
        @Override
        public boolean process(Set<? extends TypeElement> annotations,
                                                RoundEnvironment roundEnv) {
            //processing logic here
    
            //returning false means other registered processor can still
            //continue processing
            return false;
        }
    }
  5. Register the processor as SPI implementation (SPI? Explained here). Create a file META-INF/services/javax.annotation.processing.Processor and put fully qualified class name of your processor.
  6. The processor should not get invoked during it's own compilation process. We have to set javac option -proc:none (so the compiler will not try to load the processors). If we don't set this option, we will likely to get error: Bad service configuration file. Also we have to package it as jar. The client code will have that jar in it's classpath. The processor will be called during client compilation (javac) invocation.

Our processing logic will involve the two parameters of the process() method and the protected AbstractProcessor#processingEnv (an instance of ProcessingEnvironment). In next page, we will see a full example in action. Let's get familiar with the API first.


Package javax.annotation.processing



ProcessingEnvironment

In our processor, we will be using AbstractProcessor#processingEnv to access Messenger (to report error messages, warnings and notes), Filler (to write new files) and other utilities.

RoundEnvironment

The process() method is passed with the instance of this class. process() method of each registered processor is called in stages/rounds. In each round all or a subset of the registered annotations are passed to the processor. Or if new code is generated by some processor there will be more rounds. With the instance of RoundEnvironment, we can know things about last round and if the current round is last one.

Processors run in JVM

Compiler process doesn't run in JVM but it invokes a JVM process (if there are any registered processors) to start the root process. That means, we can do anything we would do in a normal java application. We can even use third party libraries/frameworks, but we cannot access the runtime information of the code which is being compiled. That means we cannot get reflective information (e.g. the class type of a field etc). The processor API provides new packages javax.lang.model, javax.lang.model.element, javax.lang.model.type and javax.lang.model.util. For now we just have to understand two things.

  • javax.lang.model.element.Element: Represents a program element such as a package, class, field or method. It has only textual construct information like element name, the enclosing/enclosed elements tree and ElementKind.
  • javax.lang.model.type: Represents type in the Java programming language. This is analogous to what we do in reflection, but still we cannot instantiate anything with it.

Package javax.lang.model.type



Package javax.lang.model.element


See Also