How does annotation processing work in Java

How does annotation processing work in Java

This article is an introduction to Annotation Processing in Java and provides an overview on Annotation Processing mechanics during compilation.

What is annotation processing

Annotation Processing can be described as an opportunity to extend the compilation process with specific behavior based primarily on metadata stored in annotations but also on source code in general.

Annotation Processing occurs during source code compilation.

The compiler will call instances of classes implementing a specific interface available in the classpath.

These objects have access to an API that allows them to read Object models of source code, to write files, to display log message as part of the compiler logs or to fail the compilation.

Commonly, Annotation Processing involves one or more of the following:

  • implementing additional source code checks
  • generating source code (classes, interfaces)
  • generating any kind of file (such as config files, documentation, …)

can not modify source code

This is something that can not be done through Annotation Processing, at least in principle. The API does not allow it.

But, it is possible and one specific framework is famous for actually doing just that: Lombok.

As described in this stackoverflow response, Lombok manages to modify source code using a hack: casting objects of the API passed to the Annotation Processor into the compiler internal objects representing the source code model and which can be modified to modify the generated bytecode.

What happens when compiling

When run, the compiler looks for Annotation Processors (see discovery mechanism below), checks what annotations each of them supports and then starts processing those annotations in a sequence of rounds.

annotation processing rounds

Each processing round is made of:

  • a list of input files
  • a list of annotations: all input files in the previous list have at least one Element with an annotation from this list
  • a list of annotation processor(s): which all have registered to at least one of the annotations in the list above (or any annotation using the * wildcard)

The compiler will create any number of round until is reaches the final round made of no input files.

My understanding is that the compiler is contractualy free to build rounds as any combination of the three lists above but with the respect of these rules:

  • an input file is processed only once, it will be part of one and only one round
  • once a Annotation Processor has been involved in a round, it will be called during every subsequent rounds
  • there is always a final round with no input file.

input files

Input files could be defined as source files with at least one Element (a package, a class/interface/enum, a method, a variable, a parameter, …) and which hasn’t yet been part of a round.

the first round

As long as it finds at least one source file with at least one annotation and as long as it is aware of at least one Annotation Processor, the compiler will create a first round of annotation processing.

Experimentaly (*), I found that all source files seems to be processed during the first round. I may not have tested with enough source files to see the
source files spawned over more than one round.

intermediary rounds

Subsequent rounds (not counting the last one which is always there) will be created only if at least one new source file with an annotation has generated by the previous round.

the last round

When there is no type in source code to process and if the previous round has generated no type at all, one last round will be created, with empty lists of input files and annotations.

This round could be seen as an opportunity for Annotation Processors to do some clean up or final source code generation.

By the way, any code generated during the last round will never be processed by the compiler, it is not called the last round for nothing.

round logs

To find out about what is going on during the Annotation Processing, how many round there are and what they are made of, logs can be enabled in Javac with command line arguments.

See this previous article for details.

the discovery process

To let the compiler know of Processor implementation to use, the Annotation Processing specification defines a Discovery Process.

One can create a file META-INF/service/javax.annotation.processing.Processor which contains one line for each Processor implementation with the qualified name of the class implementing the Processor interface.

The Compiler will automatically scan for this file in the class path when beeing run and use it to instanciate Annotation Processors.

This previous article describes how to make sure your Annotation Processor is beeing used by Javac.

(*) projects used to make thoses experiments are available online and in a project dedicated to experimenting with Annotation Processing in Java. They use Javac as a compiler.

Resources

  • the Javadoc of the Processor interface is the reference description of the Annotation Processing
  • The part about Annotation Processing in the Javac documentation
  • project Lombok uses annotations to generate content in source code such as getters/setters, toString(), constructors, …