How is an annotation processor registered and discovered by the compiler, and what do AutoService and the -processor/-proc flags do?
answer
- ServiceLoader: META-INF/services/javax.annotation.processing.Processor
- @AutoService(Processor.class) generates that file
- -processor names processors (skips discovery); -processorpath locates them
- -proc:none disables processing; -proc:only skips compilation
- Gradle annotationProcessor / Maven annotationProcessorPaths keep it build-time only
basics
~10 sThe compiler finds processors via the ServiceLoader file META-INF/services/javax.annotation.processing.Processor listing your class. Google AutoService generates that file for you. You can also name processors explicitly with javac's -processor flag, or disable processing with -proc:none.
solid answer
~40 sBy default the compiler auto-discovers processors on the processor/class path using the ServiceLoader mechanism: a text file at META-INF/services/javax.annotation.processing.Processor whose lines are the fully-qualified names of your Processor classes. Maintaining that file by hand is error-prone, so Google's AutoService library generates it: you put @AutoService(Processor.class) on your processor and its own processor writes the services file at build time. For control, javac flags matter: -processor com.example.MyProcessor names processors explicitly (skipping discovery); -processorpath sets where to look for them; -proc:none disables annotation processing entirely (a common speed/safety setting); -proc:only runs processing without compiling. Build tools surface these via configurations like Maven's annotationProcessorPaths or Gradle's annotationProcessor dependency scope, which keep processors off the runtime classpath.
code
java · 23 lines// 1) Manual registration: file on the classpath
// META-INF/services/javax.annotation.processing.Processor
// contents (one FQCN per line):
// com.example.HelloProcessor
// 2) Or let AutoService generate that file:
import com.google.auto.service.AutoService;
import javax.annotation.processing.Processor;
@AutoService(Processor.class) // AutoService writes the services file
public final class HelloProcessor extends AbstractProcessor {
// ...
}
// 3) Build-tool wiring keeps the processor build-time only:
// Gradle:
// annotationProcessor 'com.google.auto.service:auto-service:1.1.1'
// compileOnly 'com.google.auto.service:auto-service-annotations:1.1.1'
// annotationProcessor project(':hello-processor')
//
// javac flags:
// javac -processorpath proc.jar -processor com.example.HelloProcessor Foo.java
// javac -proc:none Foo.java // disable processing entirelygo deeper
Knows a processor must be registered and that @AutoService or a META-INF/services file does it.
Explains ServiceLoader discovery, AutoService's role, and the annotationProcessor/processorPaths build wiring.
Knows the javac flags (-processor/-processorpath/-proc:none/-proc:only), keeps processors off runtime path, and debugs non-running processors.
Reasons about processor-path isolation, build-tool configuration at scale, JDK changes to implicit processing, and dependency/version hygiene for tooling.
## The default mechanism: ServiceLoader discovery Java's standard plugin-discovery mechanism is **`ServiceLoader`**: a JAR declares that it provides implementations of some service interface by listing them in a text file under `META-INF/services/`, named after the fully-qualified interface name. For annotation processors the service interface is `javax.annotation.processing.Processor`, so the file is: ``` META-INF/services/javax.annotation.processing.Processor ``` and each line is the fully-qualified name of one processor class, e.g. `com.example.HelloProcessor`. When javac compiles your *main* code, it scans the **processor path** (or class path) for these files, loads each listed class, and runs it. That is how a processor in a dependency JAR 'just works' without you configuring anything in your own build beyond having the JAR on the path. ## AutoService: stop hand-writing the file Hand-maintaining that services file is easy to get wrong (typos, forgetting to update it). **Google AutoService** (`com.google.auto.service:auto-service`) solves it with — fittingly — an annotation processor of its own. You annotate your processor: ```java @AutoService(Processor.class) public final class HelloProcessor extends AbstractProcessor { ... } ``` At build time, AutoService's processor reads `@AutoService` and **generates the `META-INF/services/...Processor` file** for you with the right class name. (It's general — `@AutoService(AnyServiceInterface.class)` works for any ServiceLoader service, not just processors.) ## javac flags that control processing Even with discovery, you sometimes want explicit control: - **`-processor com.example.A,com.example.B`** — run exactly these processors and **skip ServiceLoader discovery**. Useful to pin behavior or avoid scanning. - **`-processorpath <path>`** — where to look for processors (separate from the compile classpath). Keeping processors here means they aren't on your application's runtime classpath. - **`-proc:none`** — turn annotation processing **off** entirely. Frequently used to speed up builds or compile code that shouldn't trigger processors. (Note: newer JDKs disable implicit annotation processing by default and warn unless you opt in, partly to avoid surprise processors running.) - **`-proc:only`** — run **processing but not** the subsequent compilation of the original sources. - **`-XprintProcessorInfo` / `-Xprint`** — diagnostics about which processors ran. ## How build tools express this You rarely call javac directly. Build tools map to these flags: - **Maven**: `maven-compiler-plugin` with `<annotationProcessorPaths>` (sets the processor path) and `<annotationProcessors>` (the `-processor` list). This keeps the processor off the runtime/compile classpath of your artifact. - **Gradle**: the **`annotationProcessor`** dependency configuration puts a JAR on the processor path only, e.g. `annotationProcessor 'com.google.dagger:dagger-compiler:...'`. Putting it in `implementation` instead would leak it into runtime. ## Why keep processors off the runtime path? A processor is build-time-only tooling. Declaring it in `annotationProcessor`/`annotationProcessorPaths` (not `implementation`) means your shipped artifact doesn't carry the processor and its dependencies (JavaPoet, etc.), keeping the runtime lean and avoiding version conflicts. ## Summary - Default discovery = **ServiceLoader** file `META-INF/services/javax.annotation.processing.Processor`. - **AutoService** auto-generates that file from `@AutoService(Processor.class)`. - **`-processor`** names processors explicitly (skips discovery); **`-proc:none`** disables processing; **`-processorpath`** locates them. - Build tools expose this via `annotationProcessor` (Gradle) / `annotationProcessorPaths` (Maven), keeping processors build-time only.
- My processor compiles but never runs — what's the first thing to check?Registration: is META-INF/services/javax.annotation.processing.Processor present and listing the FQCN (or is @AutoService applied)? Also confirm it's on the annotationProcessor/processor path and that -proc:none isn't set.
- What does @AutoService actually do?It's an annotation whose own processor generates the META-INF/services file at build time, so you don't hand-maintain the ServiceLoader registration. It works for any service interface, not just Processor.
- Why declare a processor in Gradle's annotationProcessor configuration rather than implementation?It places the processor on the compile-time processor path only, so it (and its deps like JavaPoet) don't leak into your runtime classpath/shipped artifact.
saying these in an interview costs you the question
- Thinking processors are found by package scanning rather than the ServiceLoader file
- Putting the processor in 'implementation'/runtime classpath instead of annotationProcessor
- Forgetting to register at all, so the processor silently never runs
- Confusing -proc:none (disable) with -proc:only (process, don't compile)
- Assuming AutoService is required — the manual services file works too