How Lombok Generates Code at Compile Time: A Deep Dive into Annotations and APT

This article explains what Lombok is, how it uses compile‑time annotations and the Java Annotation Processing Tool (APT) to automatically generate getters, setters, constructors and other boilerplate code, and provides step‑by‑step examples of defining custom annotations, processors, and compiling them to produce new classes.

Java Architect Essentials
Java Architect Essentials
Java Architect Essentials
How Lombok Generates Code at Compile Time: A Deep Dive into Annotations and APT

Lombok Overview

Lombok is an open‑source Java library that reduces boilerplate by generating code such as getters, setters, constructors, and more through simple annotations. When a class is compiled, Lombok’s annotation processor modifies the abstract syntax tree (AST) to insert the missing methods.

Using Lombok in a Gradle Project

compile ("org.projectlombok:lombok:1.16.6")

Basic Example

1 @Data
2 public class TestLombok {
3   private String name;
4   private Integer age;
5 
6   public static void main(String[] args) {
7     TestLombok testLombok = new TestLombok();
8     testLombok.setAge(12);
9     testLombok.setName("zs");
10   }
11 }

Because the class is annotated with @Data, Lombok generates the corresponding getName, setName, getAge and setAge methods in the compiled .class file, even though they are not written in the source.

Compile‑time Annotations

“Annotations (also called metadata) provide a formal way to add information to code that can be processed later.” — *Thinking in Java*

Java distinguishes between runtime annotations, which are retained in the class file and accessible via reflection, and compile‑time annotations, which are processed by an annotation processor during compilation. Lombok relies on the latter.

Annotation Processing Tool (APT)

APT (Annotation Processing Tool) is a Sun‑provided utility that operates on Java source files, allowing developers to read annotations and generate additional source or resource files during compilation.

APT runs as part of the Java compiler (javac). It scans for annotations, gathers metadata, and can emit new Java files, XML, or other resources before the compiler produces the final bytecode. This approach avoids the runtime overhead of reflection.

Defining a Custom Annotation

1 @Retention(RetentionPolicy.SOURCE)
2 @Target(ElementType.TYPE)
3 public @interface GeneratePrint {
4 
5   String value();
6 }

The @Retention element specifies how long the annotation is retained. SOURCE means the annotation is discarded by the compiler after processing. @Target restricts where the annotation can be applied (here, to types).

Defining an Annotation Processor

1 @SupportedSourceVersion(SourceVersion.RELEASE_8)
2 @SupportedAnnotationTypes("aboutjava.annotion.MyGetter")
3 public class MyGetterProcessor extends AbstractProcessor {
4   @Override
5   public synchronized void init(ProcessingEnvironment processingEnv) {
6     super.init(processingEnv);
7   }
8 
9   @Override
10   public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
11     StringBuilder builder = new StringBuilder()
12       .append("package aboutjava.annotion;

")
13       .append("public class GeneratedClass {

")
14       .append("\tpublic String getMessage() {
")
15       .append("\t\treturn \"");
16 
17     for (Element element : roundEnv.getElementsAnnotatedWith(MyGetter.class)) {
18       String objectType = element.getSimpleName().toString();
19       builder.append(objectType).append(" says hello!\
");
20     }
21 
22     builder.append("\";
")
23       .append("\t}
")
24       .append("}
");
25 
26     try {
27       JavaFileObject source = processingEnv.getFiler().createSourceFile("aboutjava.annotion.GeneratedClass");
28       Writer writer = source.openWriter();
29       writer.write(builder.toString());
30       writer.flush();
31       writer.close();
32     } catch (IOException e) {
33       // handle exception
34     }
35     return true;
36   }
37 }

The processor overrides init to obtain the compilation environment and process to generate a new class called GeneratedClass that contains a getMessage method built from the annotated elements.

Using the Custom Annotation

1 @MyGetter
2 public class TestAno {
3 
4   public static void main(String[] args) {
5     System.out.printf("1");
6   }
7 }

Compile the annotation and processor first, then compile the test class with the processor specified:

javac aboutjava/annotion/MyGetter.java
javac -processor aboutjava.annotion.MyGetterProcessor aboutjava/annotion/TestAno.java

The compilation generates a new GeneratedClass.java file, which can be inspected in the output directory. The following screenshot shows the generated source:

Generated class output
Generated class output
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaAnnotation ProcessingLombokAPTCompile-time
Java Architect Essentials
Written by

Java Architect Essentials

Committed to sharing quality articles and tutorials to help Java programmers progress from junior to mid-level to senior architect. We curate high-quality learning resources, interview questions, videos, and projects from across the internet to help you systematically improve your Java architecture skills. Follow and reply '1024' to get Java programming resources. Learn together, grow together.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.