skip to content

Annotations & Processing

Built-in annotations, the meta-annotations that configure your own, how to declare custom ones, and compile-time annotation processing. Interviewers use it to check you understand how frameworks read your code.

part ofJavaoverview, primer and where to startread it →
on this pageshow

questions

22

What is annotation processing (APT) in Java, and what can and cannot a processor do during compilation?

level: juniorimportance: must knowfreq 62%

answer

  1. Compiler plugin, runs at compile time
  2. Can only ADD new files (Filer), never edit existing source
  3. Generated files re-processed in later rounds
  4. Diagnostics via Messager can fail the build
  5. Lombok is the AST-mutating exception, not normal APT

basics

~10 s

Annotation processing is a hook in the Java compiler that runs your code during compilation. It reads annotations in the source, and can generate brand-new source files. It cannot change existing files.

solid answer

~40 s

Annotation processing (APT) is a compiler plugin mechanism, part of javac, that lets you run code at compile time. The compiler discovers Processor implementations, hands them the annotated program elements, and lets them inspect the code's structure and emit diagnostics or generate new files. The golden rule is that a processor can only create new source or resource files (via the Filer); it cannot modify or delete existing source files. Generated files are themselves fed back through another processing round so their annotations get processed too. This is how Dagger, MapStruct, and AutoValue produce boilerplate at compile time with zero runtime reflection cost. Lombok is the well-known exception: it abuses internal compiler APIs to mutate the AST of existing classes, which is not standard APT behavior.

go deeper

for a junior

Knows APT runs at compile time and generates new code from annotations; can name a tool like Lombok or a builder generator.

for a middle

States the can-add/cannot-modify rule, knows files are generated via the Filer and re-processed in rounds, and can distinguish APT from runtime reflection.

for a senior

Explains the round model, the additive constraint and why it exists, the Messager-fails-build behavior, and that Lombok is an AST-hack exception.

for a principal

Can reason about build-tooling tradeoffs (compile-time generation vs reflection vs bytecode weaving), incremental-compilation implications, and why the additive constraint matters for tooling correctness.

## What problem does this solve? In Java you often need *boilerplate*: equals/hashCode, builders, dependency-injection wiring, DTO mappers. Writing it by hand is error-prone; generating it at **runtime** with reflection is slow and hides errors until the program runs. **Annotation processing** lets a build step generate that code at **compile time**, so it is type-checked, fast, and visible. ## Key terms (defined from scratch) - **Annotation**: metadata you attach to code, e.g. `@Override`, `@Entity`, or a custom `@AutoValue`. It does nothing by itself; something must *read* it. - **javac**: the standard Java compiler. - **APT (Annotation Processing Tool)**: historically a separate tool; since Java 6 it is built into javac. It is the phase where javac discovers and runs *annotation processors*. - **Annotation processor**: a class you write that implements the `javax.annotation.processing.Processor` interface (usually by extending `AbstractProcessor`). The compiler instantiates it and calls it. - **Round**: processing happens in repeated passes. Each pass is a **round**. If round 1 generates new source files, the compiler runs another round so those new files are also analyzed and their annotations processed. This continues until a round produces no new files. - **Filer**: the API a processor uses to *write* new files (source `.java`, class files, or arbitrary resources). It is the only sanctioned way to output. ## What a processor CAN do 1. **Inspect** the structure of the code being compiled — classes, methods, fields, their modifiers, types, and the annotations on them — through the *element model* (see other questions). 2. **Generate new files** via the `Filer`: most commonly new `.java` source files. Those new files are compiled and re-processed in later rounds. 3. **Report diagnostics** via the `Messager` — notes, warnings, and *errors that fail the build*. This is how a processor enforces rules (e.g. "@AutoValue must be on an abstract class"). ## What a processor CANNOT do (the golden rule) A standard processor **cannot modify or delete existing source files**. APT is purely *additive*. You cannot, in standard APT, add a method to an existing class or rewrite a method body. This constraint is deliberate: it keeps the source you wrote authoritative and the generation predictable. - This is exactly why frameworks generate a *new* class (e.g. `AutoValue_Foo extends Foo`, `FooMapperImpl implements FooMapper`) instead of editing yours. - **Lombok** appears to violate this — `@Getter` really does add methods *to your class*. It does so by reaching into javac's internal AST (abstract syntax tree) APIs, which is unsupported, version-fragile hackery, not normal annotation processing. ## How it runs in the build During compilation javac looks on the classpath/processor-path for registered processors (via `META-INF/services/javax.annotation.processing.Processor`), instantiates each, calls `init`, then calls `process` once per round. You can disable processing entirely with `-proc:none`, run *only* processing with `-proc:only`, or name processors explicitly with `-processor`. ## Why it matters Compile-time generation gives you reflection-free, type-safe boilerplate: Dagger wires DI graphs, MapStruct writes mappers, AutoValue/Immutables write value classes — all checked by the compiler and with no runtime startup cost.

  • Why can't a processor edit an existing source file, and how do frameworks work around it?
    APT is intentionally additive to keep your source authoritative and generation predictable. Frameworks generate a sibling/subclass (e.g. AutoValue_Foo extends Foo, FooMapperImpl) instead of editing your class.
  • How does Lombok differ from a normal annotation processor?
    Lombok registers as a processor but reaches into javac's internal AST APIs to mutate existing classes (add getters/setters inline). That is unsupported, compiler-version-fragile, and not standard APT, which can only emit new files.

saying these in an interview costs you the question

  • Claiming a standard processor can modify or add methods to an existing class (only Lombok hacks do that, via internal APIs)
  • Confusing APT with runtime reflection on annotations
  • Thinking generated files are NOT re-processed
  • Saying APT runs at runtime

context

open as a page

What does the @Override annotation do, and why should you use it even though it's optional?

level: juniorimportance: must knowfreq 78%

basics

~20 s

@Override tells the compiler a method is meant to replace one from a parent class or interface. If it doesn't actually match a parent method, you get a compile error. It catches typos and wrong signatures early.

open as a page

How do you declare a custom annotation in Java, and what makes its syntax different from a regular interface?

level: juniorimportance: must knowfreq 70%

basics

~10 s

You declare a custom annotation with the @interface keyword instead of interface. Inside, each method (with no body) becomes a configurable element you can set when you use the annotation, like @MyAnno(name = "x").

open as a page

Walk through how you implement a processor with AbstractProcessor: which annotations/methods configure it, and how process() and RoundEnvironment drive the work?

level: middleimportance: must knowfreq 58%

basics

~10 s

Extend 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.

open as a page

What types are allowed for annotation elements, and what restrictions apply to them?

level: middleimportance: must knowfreq 60%

basics

~10 s

An element's type can be a primitive, String, Class, an enum, another annotation, or a single-dimension array of those. You can't use arbitrary objects, collections like List, or nested arrays.

open as a page

What are the three @Retention policies in Java (SOURCE, CLASS, RUNTIME) and how does each affect when an annotation is available?

level: middleimportance: must knowfreq 70%

basics

~20 s

@Retention says how long an annotation is kept. SOURCE: discarded after compiling. CLASS (the default): stored in the .class file but not loaded at runtime. RUNTIME: kept and readable via reflection while the program runs.

open as a page

When designing a custom annotation, how do @Retention and @Target shape whether and where it can be used, and what are the consequences of getting them wrong?

level: seniorimportance: must knowfreq 55%

basics

~20 s

@Retention says how long the annotation survives, SOURCE, CLASS, or RUNTIME. Only RUNTIME is readable by reflection. @Target restricts which code elements it can be placed on (method, field, type, etc.). Wrong choices mean your framework can't find it or it can't be applied where you need.

open as a page

What is @Documented, and how do you read annotation values at runtime via reflection?

level: juniorimportance: should knowfreq 38%

basics

~20 s

@Documented makes an annotation show up in the generated Javadoc of whatever it is applied to. To read an annotation's values while the program runs, you use reflection (getAnnotation) and call the annotation's element methods — but only if it is kept at RUNTIME.

open as a page

How do you generate code from a processor using the Filer (and JavaPoet), and how do you report errors with the Messager?

level: middleimportance: should knowfreq 44%

basics

~20 s

The Filer creates new files: call filer.createSourceFile(name) and write Java text to it. JavaPoet is a library that builds the Java source for you with a typed API instead of string concatenation. For problems, use the Messager to print an ERROR tied to the offending element, which fails the build.

open as a page

How is an annotation processor registered and discovered by the compiler, and what do AutoService and the -processor/-proc flags do?

level: middleimportance: should knowfreq 40%

basics

~10 s

The 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.

open as a page

How does @Deprecated work, and what do its since and forRemoval elements mean?

level: middleimportance: should knowfreq 58%

basics

~20 s

@Deprecated marks code that should no longer be used. The compiler warns anyone who uses it. Since Java 9 you can add since="version" to say when it was deprecated and forRemoval=true to warn it will be deleted in a future release.

open as a page

What does @FunctionalInterface enforce, and when would you use it?

level: middleimportance: should knowfreq 62%

basics

~20 s

@FunctionalInterface marks an interface that has exactly one abstract method, so it can be used with lambdas. If you add a second abstract method, the code won't compile. It's optional — it just documents intent and guards the contract.

open as a page

What is @SuppressWarnings, how is it scoped, and what are common warning categories?

level: middleimportance: should knowfreq 55%

basics

~20 s

@SuppressWarnings tells the compiler to stop showing specific named warnings for the element it's on, like @SuppressWarnings("unchecked"). Apply it as narrowly as possible (one method or variable), not on a whole class, so you don't hide real problems.

open as a page

How do default values work for annotation elements, and when can you omit an element when applying the annotation?

level: middleimportance: should knowfreq 45%

basics

~20 s

Add 'default <value>' after an element to give it a default. Then you can leave that element out when using the annotation and it takes the default. Elements without a default must always be supplied.

open as a page

What is special about an annotation element named 'value', and how does it enable the @Anno("x") shorthand?

level: middleimportance: should knowfreq 50%

basics

~10 s

If an element is named exactly 'value', you can set it without naming it: @Anno("x") means @Anno(value = "x"). It works only when value is the only element you need to supply.

open as a page

What does @Target do, and what happens if a custom annotation has no @Target?

level: middleimportance: should knowfreq 55%

basics

~10 s

@Target restricts which program elements an annotation can be placed on, like methods or fields, using ElementType values. With no @Target, the annotation can be used almost everywhere.

open as a page

Explain the javax.lang.model element and type model: Element vs TypeMirror, the main Element kinds, and the role of the Elements/Types utilities and the visitor pattern.

level: seniorimportance: should knowfreq 48%

basics

~20 s

Element represents a declaration (a class, method, field), while TypeMirror represents a type (like List<String>). TypeElement is a class/interface, ExecutableElement a method, VariableElement a field/parameter. The Elements and Types helper objects answer questions about them, and visitors traverse them safely.

open as a page

What problem does @SafeVarargs solve, and where can it be applied?

level: seniorimportance: should knowfreq 38%

basics

~20 s

When a method takes a varargs of a generic type, the compiler warns it might not be type-safe because of how generics work. @SafeVarargs is you promising the method doesn't misuse that array, which removes the warning at the method and at every call site.

open as a page

What does @Inherited do, and what are its precise limitations?

level: seniorimportance: should knowfreq 45%

basics

~10 s

@Inherited makes a class-level annotation be seen on subclasses too, when you ask via reflection. It only works for annotations on classes — not on methods, fields, or interfaces.

open as a page

How does @Repeatable work, and what is the role of the container annotation?

level: seniorimportance: should knowfreq 40%

basics

~10 s

@Repeatable (Java 8) lets you put the same annotation on one element multiple times. You also declare a 'container' annotation that holds an array of them; the compiler bundles the repeats into that container.

open as a page

Compare compile-time annotation processing with bytecode manipulation (e.g. Lombok) and runtime reflection. When would you choose each, and what are the tradeoffs?

level: principalimportance: nice to knowfreq 30%

basics

~20 s

Annotation processing generates new code at compile time, so it's type-safe and adds no runtime cost. Lombok rewrites your existing classes by hacking the compiler's internals. Reflection inspects code at runtime, which is flexible but slower and less safe. Pick generation for boilerplate, reflection only when behavior must be dynamic.

open as a page

How would you decide between a marker annotation, a single-value annotation, and a multi-element parameterized annotation when designing an API, and what tradeoffs matter for evolution?

level: principalimportance: nice to knowfreq 30%

basics

~20 s

Use a marker (no elements) when presence alone is the signal. Use a single value element when there's one obvious attribute (enables @Anno("x")). Use multiple elements with defaults for richer config. Give every non-primary element a default so you can add more later without breaking callers.

open as a page