Java Annotation Processing and Creating a Builder – Java注释处理和创建生成器

最后修改: 2016年 9月 30日

1. Introduction


This article is an intro to Java source-level annotation processing and provides examples of using this technique for generating additional source files during compilation.


2. Applications of Annotation Processing


The source-level annotation processing first appeared in Java 5. It is a handy technique for generating additional source files during the compilation stage.

源码级注解处理首次出现在Java 5中。它是一种方便的技术,用于在编译阶段生成额外的源文件。

The source files don’t have to be Java files — you can generate any kind of description, metadata, documentation, resources, or any other type of files, based on annotations in your source code.


Annotation processing is actively used in many ubiquitous Java libraries, for instance, to generate metaclasses in QueryDSL and JPA, to augment classes with boilerplate code in Lombok library.


An important thing to note is the limitation of the annotation processing API — it can only be used to generate new files, not to change existing ones.


The notable exception is the Lombok library which uses annotation processing as a bootstrapping mechanism to include itself into the compilation process and modify the AST via some internal compiler APIs. This hacky technique has nothing to do with the intended purpose of annotation processing and therefore is not discussed in this article.


3. Annotation Processing API


The annotation processing is done in multiple rounds. Each round starts with the compiler searching for the annotations in the source files and choosing the annotation processors suited for these annotations. Each annotation processor, in turn, is called on the corresponding sources.


If any files are generated during this process, another round is started with the generated files as its input. This process continues until no new files are generated during the processing stage.


Each annotation processor, in turn, is called on the corresponding sources. If any files are generated during this process, another round is started with the generated files as its input. This process continues until no new files are generated during the processing stage.


The annotation processing API is located in the javax.annotation.processing package. The main interface that you’ll have to implement is the Processor interface, which has a partial implementation in the form of AbstractProcessor class. This class is the one we’re going to extend to create our own annotation processor.


4. Setting Up the Project


To demonstrate the possibilities of annotation processing, we will develop a simple processor for generating fluent object builders for annotated classes.


We’re going to split our project into two Maven modules. One of them, annotation-processor module, will contain the processor itself together with the annotation, and another, the annotation-user module, will contain the annotated class. This is a typical use case of annotation processing.


The settings for the annotation-processor module are as follows. We’re going to use the Google’s auto-service library to generate processor metadata file which will be discussed later, and the maven-compiler-plugin tuned for the Java 8 source code. The versions of these dependencies are extracted to the properties section.

annotation-processor模块的设置如下。我们将使用谷歌的自动服务库来生成处理器元数据文件,这一点将在后面讨论,而maven-compiler-plugin为Java 8源代码进行了调整。这些依赖关系的版本被提取到属性部分。

Latest versions of the auto-service library and maven-compiler-plugin can be found in Maven Central repository:

自动服务库和maven-compiler-plugin的最新版本可以在Maven Central资源库中找到。








The annotation-user Maven module with the annotated sources does not need any special tuning, except adding a dependency on the annotation-processor module in the dependencies section:

带有注释源的annotation-user Maven模块不需要任何特殊调整,只需在依赖关系部分添加对注释处理器模块的依赖即可。


5. Defining an Annotation


Suppose we have a simple POJO class in our annotation-user module with several fields:


public class Person {

    private int age;

    private String name;

    // getters and setters …


We want to create a builder helper class to instantiate the Person class more fluently:


Person person = new PersonBuilder()

This PersonBuilder class is an obvious choice for a generation, as its structure is completely defined by the Person setter methods.


Let’s create a @BuilderProperty annotation in the annotation-processor module for the setter methods. It will allow us to generate the Builder class for each class that has its setter methods annotated:


public @interface BuilderProperty {

The @Target annotation with the ElementType.METHOD parameter ensures that this annotation can be only put on a method.


The SOURCE retention policy means that this annotation is only available during source processing and is not available at runtime.


The Person class with properties annotated with the @BuilderProperty annotation will look as follows:


public class Person {

    private int age;

    private String name;

    public void setAge(int age) {
        this.age = age;

    public void setName(String name) { = name;

    // getters …


6. Implementing a Processor


6.1. Creating an AbstractProcessor Subclass


We’ll start with extending the AbstractProcessor class inside the annotation-processor Maven module.

我们将从扩展annotation-processor Maven模块内的AbstractProcessor类开始。

First, we should specify annotations that this processor is capable of processing, and also the supported source code version. This can be done either by implementing the methods getSupportedAnnotationTypes and getSupportedSourceVersion of the Processor interface or by annotating your class with @SupportedAnnotationTypes and @SupportedSourceVersion annotations.


The @AutoService annotation is a part of the auto-service library and allows to generate the processor metadata which will be explained in the following sections.


public class BuilderProcessor extends AbstractProcessor {

    public boolean process(Set<? extends TypeElement> annotations, 
      RoundEnvironment roundEnv) {
        return false;

You can specify not only the concrete annotation class names but also wildcards, like “com.baeldung.annotation.*” to process annotations inside the com.baeldung.annotation package and all its sub packages, or even “*” to process all annotations.


The single method that we’ll have to implement is the process method that does the processing itself. It is called by the compiler for every source file containing the matching annotations.


Annotations are passed as the first Set<? extends TypeElement> annotations argument, and the information about the current processing round is passed as the RoundEnviroment roundEnv argument.

注释作为第一个Set<? extends TypeElement> 注释参数被传递,关于当前处理轮的信息作为RoundEnviroment roundEnv参数被传递。

The return boolean value should be true if your annotation processor has processed all the passed annotations, and you don’t want them to be passed to other annotation processors down the list.


6.2. Gathering Data


Our processor does not really do anything useful yet, so let’s fill it with code.


First, we’ll need to iterate through all annotation types that are found in the class — in our case, the annotations set will have a single element corresponding to the @BuilderProperty annotation, even if this annotation occurs multiple times in the source file.


Still, it’s better to implement the process method as an iteration cycle, for completeness sake:


public boolean process(Set<? extends TypeElement> annotations, 
  RoundEnvironment roundEnv) {

    for (TypeElement annotation : annotations) {
        Set<? extends Element> annotatedElements 
          = roundEnv.getElementsAnnotatedWith(annotation);
        // …

    return true;

In this code, we use the RoundEnvironment instance to receive all elements annotated with the @BuilderProperty annotation. In the case of the Person class, these elements correspond to the setName and setAge methods.


@BuilderProperty annotation’s user could erroneously annotate methods that are not actually setters. The setter method name should start with set, and the method should receive a single argument. So let’s separate the wheat from the chaff.

@BuilderProperty 注解的用户可能会错误地注解那些实际上不是设置器的方法。设置器方法的名称应该以set开头,并且该方法应该接收一个参数。所以让我们把麦子和糠秕分开。

In the following code, we use the Collectors.partitioningBy() collector to split annotated methods into two collections: correctly annotated setters and other erroneously annotated methods:


Map<Boolean, List<Element>> annotatedMethods =
  Collectors.partitioningBy(element ->
    ((ExecutableType) element.asType()).getParameterTypes().size() == 1
    && element.getSimpleName().toString().startsWith("set")));

List<Element> setters = annotatedMethods.get(true);
List<Element> otherMethods = annotatedMethods.get(false);

Here we use the Element.asType() method to receive an instance of the TypeMirror class which gives us some ability to introspect types even though we are only at the source processing stage.


We should warn the user about incorrectly annotated methods, so let’s use the Messager instance accessible from the AbstractProcessor.processingEnv protected field. The following lines will output an error for each erroneously annotated element during the source processing stage:


otherMethods.forEach(element ->
    "@BuilderProperty must be applied to a setXxx method " 
      + "with a single argument", element));

Of course, if the correct setters collection is empty, there is no point of continuing the current type element set iteration:


if (setters.isEmpty()) {

If the setters collection has at least one element, we’re going to use it to get the fully qualified class name from the enclosing element, which in case of the setter method appears to be the source class itself:


String className = ((TypeElement) setters.get(0)

The last bit of information we need to generate a builder class is a map between the names of the setters and the names of their argument types:


Map<String, String> setterMap =
    setter -> setter.getSimpleName().toString(),
    setter -> ((ExecutableType) setter.asType())

6.3. Generating the Output File


Now we have all the information we need to generate a builder class: the name of the source class, all its setter names, and their argument types.


To generate the output file, we’ll use the Filer instance provided again by the object in the AbstractProcessor.processingEnv protected property:


JavaFileObject builderFile = processingEnv.getFiler()
try (PrintWriter out = new PrintWriter(builderFile.openWriter())) {
    // writing generated file to out …

The complete code of the writeBuilderFile method is provided below. We only need to calculate the package name, fully qualified builder class name, and simple class names for the source class and the builder class. The rest of the code is pretty straightforward.


private void writeBuilderFile(
  String className, Map<String, String> setterMap) 
  throws IOException {

    String packageName = null;
    int lastDot = className.lastIndexOf('.');
    if (lastDot > 0) {
        packageName = className.substring(0, lastDot);

    String simpleClassName = className.substring(lastDot + 1);
    String builderClassName = className + "Builder";
    String builderSimpleClassName = builderClassName
      .substring(lastDot + 1);

    JavaFileObject builderFile = processingEnv.getFiler()
    try (PrintWriter out = new PrintWriter(builderFile.openWriter())) {

        if (packageName != null) {
            out.print("package ");

        out.print("public class ");
        out.println(" {");

        out.print("    private ");
        out.print(" object = new ");

        out.print("    public ");
        out.println(" build() {");
        out.println("        return object;");
        out.println("    }");

        setterMap.entrySet().forEach(setter -> {
            String methodName = setter.getKey();
            String argumentType = setter.getValue();

            out.print("    public ");
            out.print(" ");


            out.println(" value) {");
            out.print("        object.");
            out.println("        return this;");
            out.println("    }");


7. Running the Example


To see the code generation in action, you should either compile both modules from the common parent root or first compile the annotation-processor module and then the annotation-user module.


The generated PersonBuilder class can be found inside the annotation-user/target/generated-sources/annotations/com/baeldung/annotation/ file and should look like this:


package com.baeldung.annotation;

public class PersonBuilder {

    private Person object = new Person();

    public Person build() {
        return object;

    public PersonBuilder setName(java.lang.String value) {
        return this;

    public PersonBuilder setAge(int value) {
        return this;

8. Alternative Ways of Registering a Processor


To use your annotation processor during the compilation stage, you have several other options, depending on your use case and the tools you use.


8.1. Using the Annotation Processor Tool


The apt tool was a special command line utility for processing source files. It was a part of Java 5, but since Java 7 it was deprecated in favour of other options and removed completely in Java 8. It will not be discussed in this article.

apt工具是一个特殊的命令行工具,用于处理源文件。它是Java 5的一部分,但从Java 7开始,它就被废弃了,转而使用其他选项,并在Java 8中完全删除。本文将不讨论它。

8.2. Using the Compiler Key


The -processor compiler key is a standard JDK facility to augment the source processing stage of the compiler with your own annotation processor.


Note that the processor itself and the annotation have to be already compiled as classes in a separate compilation and present on the classpath, so the first thing you should do is:


javac com/baeldung/annotation/processor/BuilderProcessor
javac com/baeldung/annotation/processor/BuilderProperty

Then you do the actual compilation of your sources with the -processor key specifying the annotation processor class you’ve just compiled:


javac -processor com.baeldung.annotation.processor.MyProcessor

To specify several annotation processors in one go, you can separate their class names with commas, like this:


javac -processor package1.Processor1,package2.Processor2

8.3. Using Maven


The maven-compiler-plugin allows specifying annotation processors as part of its configuration.


Here’s an example of adding annotation processor for the compiler plugin. You could also specify the directory to put generated sources into, using the generatedSourcesDirectory configuration parameter.


Note that the BuilderProcessor class should already be compiled, for instance, imported from another jar in the build dependencies:





8.4. Adding a Processor Jar to the Classpath


Instead of specifying the annotation processor in the compiler options, you may simply add a specially structured jar with the processor class to the classpath of the compiler.


To pick it up automatically, the compiler has to know the name of the processor class. So you have to specify it in the META-INF/services/javax.annotation.processing.Processor file as a fully qualified class name of the processor:



You can also specify several processors from this jar to pick up automatically by separating them with a new line:



If you use Maven to build this jar and try to put this file directly into the src/main/resources/META-INF/services directory, you’ll encounter the following error:


[ERROR] Bad service configuration file, or exception thrown while 
constructing Processor object: javax.annotation.processing.Processor: 
Provider com.baeldung.annotation.processor.BuilderProcessor not found

This is because the compiler tries to use this file during the source-processing stage of the module itself when the BuilderProcessor file is not yet compiled. The file has to be either put inside another resource directory and copied to the META-INF/services directory during the resource copying stage of the Maven build, or (even better) generated during the build.


The Google auto-service library, discussed in the following section, allows generating this file using a simple annotation.


8.5. Using the Google auto-service Library


To generate the registration file automatically, you can use the @AutoService annotation from the Google’s auto-service library, like this:


public BuilderProcessor extends AbstractProcessor {
    // …

This annotation is itself processed by the annotation processor from the auto-service library. This processor generates the META-INF/services/javax.annotation.processing.Processor file containing the BuilderProcessor class name.


9. Conclusion


In this article, we’ve demonstrated source-level annotation processing using an example of generating a Builder class for a POJO. We have also provided several alternative ways of registering annotation processors in your project.


The source code for the article is available on GitHub.