Compile time processing using annotation processor


This example demonstrates how to do compile time checking of an annotated element.

The annotation

The @Setter annotation is a marker can be applied to methods. The annotation will be discarded during compilation not be available afterwards.

package annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.METHOD)
public @interface Setter {
 
}

The annotation processor

The SetterProcessor class is used by the compiler to process the annotations. It checks, if the methods annotated with the @Setter annotation are public, non-static methods with a name starting with set and having a uppercase letter as 4th letter. If one of these conditions isn't met, a error is written to the Messager. The compiler writes this to stderr, but other tools could use this information differently. E.g. the NetBeans IDE allows the user specify annotation processors that are used to display error messages in the editor.

package annotation.processor;
import annotation.Setter;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
 
@SupportedAnnotationTypes({"annotation.Setter"})
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class SetterProcessor extends AbstractProcessor {
 
	private Messager messager;
	@Override
	public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
		// get elements annotated with the @Setter annotation
		Set<? extends Element> annotatedElements = roundEnv.getElementsAnnotatedWith(Setter.class);
 
		for (Element element : annotatedElements) {
			if (element.getKind() == ElementKind.METHOD) {
				// only handle methods as targets
				checkMethod((ExecutableElement) element);
			}
		}
 
		// don't claim annotations to allow other processors to process them
		return false;
	}
	private void checkMethod(ExecutableElement method) {
		// check for valid name
		String name = method.getSimpleName().toString();
		if (!name.startsWith("set")) {
			printError(method, "setter name must start with \"set\"");
		} else if (name.length() == 3) {
			printError(method, "the method name must contain more than just \"set\"");
		} else if (Character.isLowerCase(name.charAt(3))) {
			if (method.getParameters().size() != 1) {
				printError(method, "character following \"set\" must be upper case");
			}
		}
 
		// check, if setter is public
		if (!method.getModifiers().contains(Modifier.PUBLIC)) {
			printError(method, "setter must be public");
		}
 
		// check, if method is static
		if (method.getModifiers().contains(Modifier.STATIC)) {
			printError(method, "setter must not be static");
		}
	}
 
	private void printError(Element element, String message) {
		messager.printMessage(Diagnostic.Kind.ERROR, message, element);
	}
 
	@Override
	public void init(ProcessingEnvironment processingEnvironment) {
		super.init(processingEnvironment);
 
		// get messager for printing errors
		messager = processingEnvironment.getMessager();
	}
}

Packaging

To be applied by the compiler, the annotation processor needs to be made available to the SPI (see ServiceLoader).

To do this a text file META-INF/services/javax.annotation.processing.Processor needs to be added to the jar file containing the annotation processor and the annotation in addition to the other files. The file needs to include the fully qualified name of the annotation processor, i.e. it should look like this

annotation.processor.SetterProcessor

We'll assume the jar file is called AnnotationProcessor.jar below.

Example annotated class

The following class is example class in the default package with the annotations being applied to the correct elements according to the retention policy. However only the annotation processor only considers the second method a valid annotation target.

import annotation.Setter;
public class AnnotationProcessorTest {
 
	@Setter
	private void setValue(String value) {}
 
	@Setter
	public void setString(String value) {}
 
	@Setter
	public static void main(String[] args) {}
 
}

Using the annotation processor with javac

If the annotation processor is discovered using the SPI, it is automatically used to process annotated elements. E.g. compiling the AnnotationProcessorTest class using

javac -cp AnnotationProcessor.jar AnnotationProcessorTest.java

yields the following output

AnnotationProcessorTest.java:6: error: setter must be public
private void setValue(String value) {}
^
AnnotationProcessorTest.java:12: error: setter name must start with "set"
public static void main(String[] args) {}
^
2 errors

instead of compiling normally. No .class file is created.

This could be prevented by specifying the -proc:none option for javac. You could also forgo the usual compilation by specifying -proc:only instead.

Basic Programs