Java Annotation Processing

Annotation processing is Java's compile-time code generation mechanism. Annotated source code is read by an annotation processor that emits additional source code or modifies bytecode. The result is the developer-facing benefits of reflection (less boilerplate) without the runtime cost.

This page is about how annotation processing works, the major processors that have stuck, and when writing your own pays.

How it works

The Java compiler invokes annotation processors during compilation:

  1. Compiler reads annotated source
  2. Processors registered via META-INF/services/javax.annotation.processing.Processor get invoked
  3. Each processor reads the AST, can emit new source files
  4. New sources go through the same compilation rounds

The output: code generated at build time that the user never writes manually but uses directly.

The major processors

Lombok

The most-used Java annotation processor. Annotations like @Getter, @Setter, @Builder, @Data, @Slf4j generate the corresponding boilerplate at compile time.

@Data
public class User {
    private final String email;
    private int loginCount;
}

// Lombok generates:
//   - constructor
//   - getEmail(), getLoginCount(), setLoginCount()
//   - equals(), hashCode(), toString()

The trade-offs:

For new code, prefer records over @Data. Lombok still has uses (@Slf4j, @Builder on non-records, @With), but the case for @Data has weakened.

MapStruct

Generates type-safe mappers between similar types — typically DTO ↔ entity:

@Mapper
public interface OrderMapper {
    OrderDTO toDto(Order order);
    Order toEntity(OrderDTO dto);
}

MapStruct generates the implementation at compile time. Faster than reflection-based mapping (e.g., ModelMapper), with errors at compile time rather than runtime.

Dagger / Hilt

Compile-time dependency injection. Faster than Spring at startup; generates code that does the wiring. Common in Android and performance-sensitive backends.

The trade-off: less flexible than runtime DI; the dependency graph must be expressible at compile time.

Annotation processors in frameworks

Many frameworks use annotation processors:

When to write your own

Most teams should not. The cases where it pays:

The cost:

Patterns to avoid

A modern reasonable position

For most Java teams:

Common failure patterns

Further Reading