Walk through how you implement a processor with AbstractProcessor: which annotations/methods configure it, and how process() and RoundEnvironment drive the work?
answer
- Extend AbstractProcessor, override process()
- @SupportedAnnotationTypes + @SupportedSourceVersion (or latestSupported())
- init() gives Filer/Messager/Elements/Types — stash them
- RoundEnvironment.getElementsAnnotatedWith() per round
- return true = claim annotations; processingOver() = final round
basics
~10 sExtend AbstractProcessor, declare which annotations you handle with @SupportedAnnotationTypes and the Java version with @SupportedSourceVersion. Override process(), where RoundEnvironment.getElementsAnnotatedWith() gives you the annotated code to read and generate from.
solid answer
~40 sYou normally extend AbstractProcessor rather than implementing Processor directly. You declare the annotations you care about with @SupportedAnnotationTypes (or override getSupportedAnnotationTypes) and the supported Java version with @SupportedSourceVersion (commonly latestSupported()). The compiler calls init(ProcessingEnvironment) once, giving you the Filer, Messager, Elements, and Types utilities, which you stash. Then it calls process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) once per round. Inside process you call roundEnv.getElementsAnnotatedWith(MyAnnotation.class) to get the annotated Elements, inspect them via the element/type model, and generate code with the Filer. Returning true 'claims' those annotations so other processors won't also handle them; returning false lets them pass through. process is invoked across multiple rounds; the final round has roundEnv.processingOver() == true and an empty element set, which is where you do any deferred or summary work.
go deeper
Can name AbstractProcessor and that you override process(); recognizes @SupportedAnnotationTypes.
Implements the full lifecycle: configuration annotations, init() tool capture, process() with getElementsAnnotatedWith, and understands multiple rounds.
Explains the claim semantics of the return value, processingOver() usage, cross-round instance state, and latestSupported() rationale.
Reasons about multi-processor cooperation, generation staging across rounds, incremental/Gradle isolating-vs-aggregating processor classification, and failure semantics.
## The contract: Processor vs AbstractProcessor The compiler talks to your code through the `javax.annotation.processing.Processor` interface. Implementing it raw is tedious, so you extend **`AbstractProcessor`**, a base class that implements the boilerplate (caching the environment, reading the configuration annotations) and leaves you one method to override: `process`. ## Step 1 — declare what you handle Two configuration annotations on your processor class: - **`@SupportedAnnotationTypes({"com.example.AutoValue"})`** — the fully-qualified names of the annotations this processor wants. Wildcards like `"com.example.*"` are allowed. (Equivalently, override `getSupportedAnnotationTypes()`.) - **`@SupportedSourceVersion(SourceVersion.RELEASE_17)`** — the newest Java language version your processor understands. Most processors override `getSupportedSourceVersion()` to return `SourceVersion.latestSupported()` so they don't emit a 'source version not supported' warning on newer JDKs. ## Step 2 — init(ProcessingEnvironment) The compiler calls `init` **once**, passing a **`ProcessingEnvironment`**. From it you obtain and store the tools you'll need every round: - **`Filer`** — to write generated files. - **`Messager`** — to print notes/warnings/errors. - **`Elements`** (`getElementUtils()`) — helpers for working with program elements (look up a type by name, get docs, package of an element, etc.). - **`Types`** (`getTypeUtils()`) — helpers for working with types (is-subtype, erasure, etc.). ## Step 3 — process(...) per round Signature: `boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv)`. - **`annotations`**: the subset of *your* supported annotations that actually appear in the code being processed this round. - **`RoundEnvironment roundEnv`**: the window into the current round. Its key methods: - `getElementsAnnotatedWith(Class/TypeElement)` → the **Elements** annotated with that annotation this round. An **Element** is a node in the program's structure: a `TypeElement` (class/interface), `ExecutableElement` (method/constructor), `VariableElement` (field/parameter), etc. - `getRootElements()` → the top-level types being compiled this round. - `processingOver()` → `true` on the final, extra round after all source has been generated; the element set is empty here. Use it for summary/validation work that needs everything generated. - `errorRaised()` → whether a prior processor reported an error. ### The return value matters `process` returns a **boolean meaning 'claimed'**: - `true` → you *claim* the annotations in `annotations`; the compiler will **not** offer them to subsequent processors. - `false` → the annotations remain available to other processors. Most single-owner processors return `false` unless they truly own an annotation exclusively; over-claiming can starve other processors. ## Rounds, concretely Round 1: your annotated source is presented; you generate `Foo_Generated.java`. Round 2: that generated file is now part of compilation; if *it* carries annotations you handle, you see them. Rounds continue until a round generates nothing. Then one **final round** runs with `processingOver() == true`. ## A minimal skeleton See the code example. Note: state that must survive across rounds (e.g. "defer this element until a type it references is generated") is kept in instance fields, because the *same processor instance* is reused across all rounds of one compilation. ## Why this shape The per-round, claim-based, tool-injected design lets many independent processors cooperate on one compilation, generate in stages, and fail the build cleanly via the Messager — all without runtime cost.
- What does returning true from process() mean, and when should you return false?true claims the annotations so no other processor handles them; false lets them pass through. Return false unless you exclusively own the annotation, to avoid starving cooperating processors.
- Why override getSupportedSourceVersion() to return latestSupported()?Otherwise the compiler warns that your declared @SupportedSourceVersion is older than the JDK in use. latestSupported() opts out of that warning by claiming support for the running compiler's newest version.
- How do you carry state between rounds?Store it in instance fields of the processor — the same instance is reused across all rounds of a single compilation, e.g. to defer elements whose dependencies aren't generated yet.
saying these in an interview costs you the question
- Thinking process() is called only once (it's once per round)
- Believing the boolean return means 'success' rather than 'claimed'
- Forgetting @SupportedSourceVersion and emitting unsupported-version warnings on new JDKs
- Trying to read processed elements during the processingOver() round (set is empty)
- Assuming a fresh processor instance per round (it's reused; instance state persists)