Annotation Processor 101 — Your First Custom Annotation

Emma Suzuki
6 min readSep 7, 2016

I am pretty sure that you all have used @Annotation. @Annotation includes @Override, @AutoValue (from Auto) @BindView (from ButterKnife), @GET (from Retrofit), etc. Some are lint purposes and some are used to wrap complicated logics in a framework. In this article, I am specifically focusing on an annotation processor for generating code; it could be used for reducing boilerplates.

Even though all of us have been using @Annotation, I think that not many developers have used the annotation processor yet. I was one of the few who wanted to start creating my library with @Annotation but I could not find lots of resources. Thus, hopefully, this article will be found by one of the developers like me and they can get started on their own framework from here.

Create Your First Annotation Processor

I will explain how to setup the annotation processor step-by-step.

  1. Create a new Android project. This should create an “app” package and I will use this app package to test a custom @Annotation.
  2. Create a processor module in the project.

Select File > New Module. When “New Module” window popups, select “Java Library” and hit Next.

Input Library name, Java package name and Java class name fields.

I use these setup in this demo.
Library name: processor
Java project name: com.emmasuzuki.annotationdemo
Java class name: MyClass (I do not use this class in this demo, so this is just a placeholder.)

3. Setup Java source and target versions.

In app/build.gradle, put these lines in “android” block.

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}

In processor/build.gradle, add these lines,

sourceCompatibility = 1.7
targetCompatibility = 1.7

4. Create an @Annotation class in the processor module.

First, I create a processor package under my root package so that processor module has “com.emmasuzuki.annotationdemo.processor” package.

Then, I will create an annotation class under this “processor” package.

5. Write the annotation class.

This is my “Greet” annotation class.

@interface tells Java that this is an annotation class.

@Retention defines when your annotation is needed.
It can be SOURCE, CLASS or RUNTIME.

SOURCE: Discard during the compile. These annotations don't make any sense after the compile has completed, so they aren't written to the bytecode.
CLASS: Discard during class load. Useful when doing bytecode-level post-processing.
RUNTIME: Do not discard. The annotation should be available for reflection at runtime.
ref: http://www.oracle.com/technetwork/articles/hunter-meta-2-098036.html

@Target defines where your annotation would be applied. It can be TYPE, FIELD, METHOD, etc. Full types can be found here.

TYPE: Class, interface or enum declaration.
FIELD: Class field declaration
METHOD: Method declaration
...

I set @Target to TYPE, so my @Greet annotation can be only applied to a class, interface or enum declaration.

Inside the Greet class, I put 1 parameter that can be passed from Annotation. I use a default label, “value”, so that I do not need to add label when I use @Greet annotation. Also, adding default makes the parameter optional.

Therefore, this @Greet annotation can be used as

@Greet                    // String array is optional
or
@Greet({"A", "B", "C"}) // Accept String array

If I change “value” to “customName”, I need to add the label name before the String array.

@Greet(customName = {"A", "B", "C"}) // Must supply label name

6. Create a processor class in the “processor” package.

This class is where you define how your custom annotation should be processed.

First add @SupportedAnnotationTypes with a full path of an annotation class which you are handling in this class. Then, add @SupportedSourceVersion to define the Java version.

Next, extend AbstractProcessor. In this class, I basically should override only process method. This method is where I write code to handle the annotation.

7. Write the process method. Let’s make a magic happen.

First at 1., I grab a package information so that I can generate a class into the same package.

PackageElement packageElement = (PackageElement) type.getEnclosingElement();
packageName = packageElement.getQualifiedName().toString();

Also I grab annotation parameters by

names = type.getAnnotation(Greet.class).value();

Then at 2., I generate the Greeter class. The Greeter class that I am trying to write is

package com.emmasuzuki.annotationdemo;public class Greeter {  public static String hello() {
return "Hello {all names separated by comma}!";
}
}

and at 4., I write the generated Greeter code into a Java file.

// Create com.emmasuzuki.annotation.demo.Greeter file
JavaFileObject javaFileObject = processingEnv.getFiler().createSourceFile(packageName + ".Greeter");
// Write contents in builder into file
Writer writer = javaFileObject.openWriter();
writer.write(builder.toString());
writer.close();

8. Add processor info. The last but not least.

Without this, the Java still does not know how to handle my @Greet annotation.

Create resources/META-INF/services folder. NOT resources/META-INF.services. This folder path has to be exact. This is how Java will find your “Processor” file.

Before you go further, double check the path. Because until you have a correct folder structure, a compiler would not complain and not tell you what’s wrong.

Once you made perfectly sure about the path, you also need to perfectly spell this file. “javax.annotation.processing.Processor”. In this file, you write a class path to your processor. In my example, the file should have this line

com.emmasuzuki.annotationdemo.processor.GreetProcessor

If there are more than 1 annotation processor, you can write multiple paths separated by newline.

With this, Java knows how to process @Greet annotation.

Let’s Test @Greet Annotation.

In order to use annotations in app folder, I add android-apt plugin.

Add the plugin in build.gradle (project),

buildscript {
...
dependencies {
...
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
}

Apply plugin in app/build.gradle,

apply plugin: 'com.neenbedankt.android-apt'

then, add processor module to dependencies in app/build.gradle,

dependencies {
...
compile project(':processor')
}

Sync project with gradle and get a cup of coffee…

Now let’s add @Greet onto any class declaration.

And build app while having another sip of coffee…

If app is built successfully,

The Greeter class should be generated under app/build/generated/source/apt/.

The generated class looks like this:

Once this class is generated, I can do

textView.setText(Greeter.hello());

in MainActivity.

HOORAY! My first annotation processor generated code and I used the generated class from my app.

Now, I showed you how to setup the annotation processor. However, this is not the best yet, this is just a start.

There are two things that I did not cover in this article:

I will cover the first one in next blog and the second one later in this series.

Hold on to that and until then try creating your first own annotation!!

--

--

Emma Suzuki

A bit of many technology, full of curiosity. Mobile, Web, Data Engineer.