Week 106 — What is an annotation processor?

Question of the Week #106
What is an annotation processor?
5 Replies
CrimsonDragon1
Annotation processors allow running Java code during compilation for validation and code generation. Annotation processors can be invoked when compiling code with specific annotations or when any class is compiled (given the annotation processor is registered and enabled). An annotation processor can be created by implementing the Processor interface which is typically done by extending AbstractProcessor. When extending AbstractProcessor, one can specify the annotations to watch using SupportedAnnotationTypes and the latest supported Java version of the source code using @SupportedSourceVersion. Annotation processing happens in multiple "rounds". Each "round" , the process method is called. If this method returns returns true, the processed annotations are "claimed" for this round resulting in other processors not processing these annotations.
// if all classes should be processed, one can use @SupportedAnnotationTypes("*")
@SupportedAnnotationTypes("com.example.MyAnnotation")
@SupportedSourceVersion(SourceVersion.RELEASE_21)
public class SomeProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
//can validate or generate code here
return false;//processed annotations are not "claimed" so other annotation processors can still run on these classes
}
}
// if all classes should be processed, one can use @SupportedAnnotationTypes("*")
@SupportedAnnotationTypes("com.example.MyAnnotation")
@SupportedSourceVersion(SourceVersion.RELEASE_21)
public class SomeProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
//can validate or generate code here
return false;//processed annotations are not "claimed" so other annotation processors can still run on these classes
}
}
Annotation processors are registered using the ServiceLoader API. This can be done by adding a provides statement in the module-info.java file:
import javax.annotation.processing.Processor;

module anntest.processor {
requires java.compiler;//necessary module for annotation processing

provides Processor with com.example.processing.SomeProcessor;//this is our annotation processor
}
import javax.annotation.processing.Processor;

module anntest.processor {
requires java.compiler;//necessary module for annotation processing

provides Processor with com.example.processing.SomeProcessor;//this is our annotation processor
}
Non-modular annotation processors can be registered by including the fully qualified name of the annotation processor in a file named javax.annotation.processing.Processor in META-INF/services. When compiling code using --processor-module-path or --processor-path pointing to the compiled code of the annotation processor, it will be run during compilation. Furthermore, it is possible to specify annotation processors explicitly using -processor followed by the fully qualified name of the annotation processor class. The -proc argument can be used to specify whether it should do compiling, annotation processing or both. -proc:none only compiles the code without running annotation processors. -proc:only runs annotation processors but does not compile code. -proc:full runs annotation processor and compiles the code.
CrimsonDragon1
The AbstractProcessor class provides a field named processingEnv that can be used to emit notes, warnings and errors or generate code.
processingEnv.getMessager().printNote("This is a note.");
processingEnv.getMessager().printWarning("This is a compiler warning.");
processingEnv.getMessager().printError("This is a compiler error.");
processingEnv.getMessager().printNote("This is a note.");
processingEnv.getMessager().printWarning("This is a compiler warning.");
processingEnv.getMessager().printError("This is a compiler error.");
@SupportedAnnotationTypes("com.example.MyAnnotation")
@SupportedSourceVersion(SourceVersion.RELEASE_21)
public class SomeProcessor extends AbstractProcessor {
private boolean processorRanAlready = false;
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if(processorRanAlready){
processingEnv.getMessager().printNote("consecutive round - don't generate any more code");
return false;
}
processorRanAlready = true;
processingEnv.getMessager().printNote("generating some code...");
try{
JavaFileObject generatedObject = processingEnv.getFiler().createSourceFile("com.example.generated.SomeGeneratedClass");
try(PrintStream ps = new PrintStream(generatedObject.openOutputStream())){
ps.printf("""
package com.example.generated;
@javax.annotation.processing.Generated(value = %s, date=%s)
public class SomeGeneratedClass{
//some code
}
""",
getClass().getCanonicalName(), OffsetDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSX")));
}
}catch(IOException e){
e.printStackTrace();
processingEnv.getMessager().printNote("An exception occured during processing");
}
return false;
}
}
@SupportedAnnotationTypes("com.example.MyAnnotation")
@SupportedSourceVersion(SourceVersion.RELEASE_21)
public class SomeProcessor extends AbstractProcessor {
private boolean processorRanAlready = false;
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if(processorRanAlready){
processingEnv.getMessager().printNote("consecutive round - don't generate any more code");
return false;
}
processorRanAlready = true;
processingEnv.getMessager().printNote("generating some code...");
try{
JavaFileObject generatedObject = processingEnv.getFiler().createSourceFile("com.example.generated.SomeGeneratedClass");
try(PrintStream ps = new PrintStream(generatedObject.openOutputStream())){
ps.printf("""
package com.example.generated;
@javax.annotation.processing.Generated(value = %s, date=%s)
public class SomeGeneratedClass{
//some code
}
""",
getClass().getCanonicalName(), OffsetDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSX")));
}
}catch(IOException e){
e.printStackTrace();
processingEnv.getMessager().printNote("An exception occured during processing");
}
return false;
}
}
📖 Sample answer from dan1st
CrimsonDragon1
Annotation processors are a way of extending the Java compiler to perform custom logic in response to annotations in your Java code. Typically, this custom logic is to generate other files (in fact, this is the official, stated purpose of annotation processors). These could be anything -- new Java source files, configuration files, etc.
CrimsonDragon1
Many tools in the Java ecosystem make use of annotation processing. For instance, Dagger is a dependency injection engine that generates static dependency information during compilation, which the runtime then uses for very performant dependency resolution and injection.
Submission from dangerously_casual
CrimsonDragon1
An annotation processor in Java operates during compilation to process specific annotations in source code. It generates additional files like source code or resources but cannot modify existing code. It’s useful for automating tasks, enforcing standards, or generating boilerplate code. Annotation processors run in rounds during compilation: 1. Initialization: Initializes with a ProcessingEnvironment. 2. Processing: Processes annotations and generates files, triggering new rounds if files are created. 3. Completion: Ends when no new files are generated. To create a processor, extend AbstractProcessor and override process, specifying supported annotations with @SupportedAnnotationTypes. Example:
@SupportedAnnotationTypes("com.example.BuilderProperty")
public class BuilderProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
// Logic here
return true;
}
}
@SupportedAnnotationTypes("com.example.BuilderProperty")
public class BuilderProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
// Logic here
return true;
}
}
Widely used in frameworks like Lombok, annotation processors enhance functionality by generating code for tasks like getters and constructors. They ensure source code remains unchanged during compilation.
⭐ Submission from davinki

Did you find this page helpful?