Prev Next

Java / Lombok Interview questions

1. What is Lombok? 2. What is the purpose of Lombok in Java? 3. How do you add Lombok to a Maven project? 4. How do you add Lombok to a Gradle project? 5. What is @Getter used for? 6. What is @Setter used for? 7. What is @ToString used for? 8. What is @EqualsAndHashCode used for? 9. What is @NoArgsConstructor used for? 10. What is @AllArgsConstructor used for? 11. What is @RequiredArgsConstructor used for? 12. What is @Data used for? 13. What is @Value used for? 14. What is @Builder used for? 15. What is @Slf4j used for? 16. What is @NonNull used for? 17. What is @Cleanup used for? 18. What is @SneakyThrows used for? 19. What is @Synchronized used for? 20. What is @With used for? 21. Describe how Lombok works under the hood (annotation processing)? 22. How do you install the Lombok plugin in an IDE? 23. What are the types of Lombok annotations? 24. How do you exclude a field from @ToString? 25. How do you exclude a field from @EqualsAndHashCode? 26. What is @Builder.Default used for? 27. Why does IDE support matter for using Lombok effectively? 28. What is the difference between @Data and @Value? 29. What is the difference between @NoArgsConstructor, @AllArgsConstructor, and @RequiredArgsConstructor? 30. When should you use @Builder instead of a constructor? 31. How does @EqualsAndHashCode handle inheritance by default? 32. Why can @Data be risky on JPA entity classes? 33. How do you combine Lombok's @Builder with inheritance? 34. What is the difference between @Getter(lazy = true) and a normal @Getter? 35. Why should you be careful using @EqualsAndHashCode on Hibernate entities with lazy-loaded proxies? 36. What is @Accessors used for and how does it change fluent-style access? 37. How does Lombok handle @ToString with circular references? 38. What is @FieldDefaults used for? 39. What is @Delegate used for? 40. Why is Lombok's approach considered a "compiler hack" and what are the implications? 41. How do you debug generated Lombok code? 42. What issues can Lombok cause with static code analysis tools? 43. What are the risks of using @Data with mutable collections? 44. How does delombok work and when would you use it? 45. Why might Lombok cause issues in multi-module Maven builds with annotation processor ordering? 46. What is @UtilityClass used for? 47. What is the difference between Lombok's @Builder and the traditional Gang-of-Four Builder pattern? 48. How do you use Lombok with Java records - is Lombok still needed?
Could not find what you were looking for? send us the question and we would be happy to answer your question.

1. What is Lombok?

Lombok is a Java library that removes boilerplate code — getters, setters, constructors, equals/hashCode, toString, and more — by generating it automatically at compile time from simple annotations, rather than you writing it by hand.

import lombok.Getter;
import lombok.Setter;

public class Person {
    @Getter @Setter
    private String name;

    @Getter @Setter
    private int age;
}

Behind the scenes, Lombok plugs into the Java compiler as an annotation processor: it reads your source file, generates the missing methods directly into the compiled bytecode, and your IDE (with the Lombok plugin installed) shows those generated methods as if they were written normally. The source file itself never actually contains the generated code — it's added during compilation.

What problem does Lombok primarily solve?
When does Lombok actually generate the boilerplate code?

2. What is the purpose of Lombok in Java?

Lombok exists to cut down the sheer amount of repetitive, mechanical code that plain Java classes typically require — particularly simple data-holding classes (often called POJOs) that need getters, setters, constructors, and standard Object method overrides just to function correctly with frameworks, collections, and equality checks.

Without Lombok, a class with five fields might need 40-plus lines of generated-looking code just for getters, setters, and a proper equals/hashCode/toString. With Lombok, a handful of annotations replace all of that, letting the class definition focus on what data it actually holds rather than the mechanical methods every field needs.

@Getter @Setter @ToString @EqualsAndHashCode
public class Product {
    private String id;
    private String name;
    private double price;
}

What kind of code does Lombok primarily reduce?
What does a class definition look like with Lombok compared to without it?

3. How do you add Lombok to a Maven project?

Lombok is added as a regular dependency, typically scoped as provided since it's only needed at compile time — the generated code becomes part of your compiled classes, so Lombok itself doesn't need to be on the runtime classpath.

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.30</version>
    <scope>provided</scope>
</dependency>

With that dependency present, Maven's compiler plugin automatically picks up Lombok as an annotation processor during the compile phase — no separate build plugin configuration is normally required, since annotation processors on the classpath are discovered automatically by javac.

What scope is Lombok's Maven dependency typically given?
Why is a runtime scope generally unnecessary for Lombok?

4. How do you add Lombok to a Gradle project?

Gradle uses the compileOnly and annotationProcessor configurations together for Lombok — compileOnly makes the annotations available while writing code, and annotationProcessor tells Gradle to actually run Lombok during compilation to generate the boilerplate.

dependencies {
    compileOnly 'org.projectlombok:lombok:1.18.30'
    annotationProcessor 'org.projectlombok:lombok:1.18.30'

    testCompileOnly 'org.projectlombok:lombok:1.18.30'
    testAnnotationProcessor 'org.projectlombok:lombok:1.18.30'
}

Both lines are necessary: omitting annotationProcessor means the annotations compile but no code actually gets generated, resulting in "cannot find symbol" errors for methods like generated getters. The test-prefixed variants are needed separately if you use Lombok annotations in test source code too.

What happens if you add compileOnly for Lombok but forget annotationProcessor?
Why are separate testCompileOnly/testAnnotationProcessor entries needed?

5. What is @Getter used for?

@Getter generates a standard getter method for a field (or every field, when applied at the class level), following the usual JavaBeans naming convention — getFieldName() for most types, or isFieldName() for a boolean field.

public class Person {
    @Getter
    private String name;

    @Getter
    private boolean active;
}
//  generates:
//  public String getName() { return name; }
//  public boolean isActive() { return active; }

Applying @Getter at the class level generates a getter for every field in that class in one line, rather than annotating each field individually — a common shortcut once a class has several fields that all need the same treatment.

What method name does @Getter generate for a boolean field named `active`?
What does applying @Getter at the class level do?

6. What is @Setter used for?

@Setter generates a standard setter method for a field, following the JavaBeans convention of setFieldName(Type value), and like @Getter, it can be applied per-field or at the class level to cover every field at once.

public class Person {
    @Setter
    private String name;
}
//  generates:
//  public void setName(String name) { this.name = name; }

A common pattern is applying @Getter at the class level but adding @Setter only to specific fields that should actually be mutable, keeping other fields effectively read-only from outside the class — useful for fields that should be set once (often via a constructor) and never changed afterward.

What method signature does @Setter generate for a field `private String name`?
Why might you apply @Getter at the class level but @Setter only on specific fields?

7. What is @ToString used for?

@ToString generates a toString() override that prints the class name followed by each field's name and value, giving a readable default representation instead of Java's default ClassName@hashcode output.

@ToString
public class Person {
    private String name;
    private int age;
}
//  toString() produces: "Person(name=Ada, age=34)"

By default it includes every field, but individual fields can be excluded with @ToString.Exclude — commonly used for sensitive fields (passwords, tokens) that shouldn't end up in logs, or for fields that would make the output unreasonably large or cause infinite recursion (like a reference back to a parent object).

What does @ToString produce compared to Java's default toString()?
How do you exclude a specific field from the generated toString()?

8. What is @EqualsAndHashCode used for?

@EqualsAndHashCode generates proper equals() and hashCode() overrides based on the class's fields, following the standard contract Java expects — two objects with equal field values are considered equal and produce the same hash code, which is essential for correct behavior in collections like HashSet or HashMap.

@EqualsAndHashCode
public class Point {
    private int x;
    private int y;
}
//  two Point(1, 2) instances are now equal, and share the same hashCode

By default it includes all non-static fields, but specific fields can be excluded with @EqualsAndHashCode.Exclude — useful for fields that shouldn't factor into equality, such as a timestamp or a cached, derived value that doesn't represent the object's actual identity.

Why is @EqualsAndHashCode important for objects used in a HashSet?
How do you exclude a field from equality/hashCode calculation?

9. What is @NoArgsConstructor used for?

@NoArgsConstructor generates a public, no-argument constructor for the class — useful for frameworks that require a default constructor to instantiate objects via reflection, such as JPA entities or JSON deserialization libraries like Jackson.

@NoArgsConstructor
public class Person {
    private String name;
    private int age;
}
//  generates: public Person() {}

If the class has final fields without default values, Lombok can't generate a truly empty constructor that leaves them properly initialized, and will either fail or require the force = true option, which initializes those fields to their default value (0, false, or null) inside the generated constructor.

Why do frameworks like JPA often require a no-argument constructor?
What happens with final fields when generating a no-args constructor?

10. What is @AllArgsConstructor used for?

@AllArgsConstructor generates a constructor that takes one parameter for every field in the class, in the order the fields are declared — a quick way to get a fully-populating constructor without writing it by hand.

@AllArgsConstructor
public class Person {
    private String name;
    private int age;
}
//  generates: public Person(String name, int age) { this.name = name; this.age = age; }

It's often combined with @Builder so the builder has a full constructor to delegate to, or with @NoArgsConstructor so the class supports both a default constructor (for frameworks) and a fully-populating one (for application code), covering both use cases with two short annotations instead of two hand-written constructors.

What does @AllArgsConstructor generate?
Why is @AllArgsConstructor often paired with @Builder?

11. What is @RequiredArgsConstructor used for?

@RequiredArgsConstructor generates a constructor that includes only the fields Lombok considers "required": final fields, and fields annotated @NonNull that don't already have a default value assigned.

@RequiredArgsConstructor
public class Person {
    private final String name;   //  required: included
    @NonNull
    private String email;        //  required: included
    private int age;             //  not required: excluded
}
//  generates: public Person(String name, String email) { ... }

This is especially common in Spring applications for constructor-based dependency injection: marking injected dependency fields as final and applying @RequiredArgsConstructor at the class level generates exactly the constructor Spring needs to inject those dependencies, without listing every other, non-dependency field.

Which fields does @RequiredArgsConstructor include in the generated constructor?
Why is @RequiredArgsConstructor popular in Spring applications?

12. What is @Data used for?

@Data is a convenience annotation that bundles several others together: @Getter, @Setter, @ToString, @EqualsAndHashCode, and @RequiredArgsConstructor, all applied at once with a single annotation on the class.

@Data
public class Person {
    private final String id;
    private String name;
    private int age;
}
//  equivalent to individually applying @Getter, @Setter (on non-final fields),
//  @ToString, @EqualsAndHashCode, and @RequiredArgsConstructor

It's a fast way to create a typical mutable data class, but it's an all-or-nothing bundle — if you need to customize or exclude just one piece of that behavior (say, excluding a field from equals), you generally either add the individual exclusion annotations alongside @Data, or drop down to the individual annotations instead of the bundle.

What is @Data essentially a shortcut for?
Does @Data generate setters for final fields?

13. What is @Value used for?

@Value is the immutable counterpart to @Data: it makes the class and all its fields final by default, generates getters (but no setters), a constructor covering every field, toString(), and equals()/hashCode() — producing a fully immutable value object from a single annotation.

@Value
public class Point {
    int x;
    int y;
}
//  fields become private final; only getters are generated, no setters;
//  class itself becomes final

It's the right fit for genuinely immutable value types (coordinates, money amounts, identifiers) where you want the compiler to enforce that instances can't change after construction, rather than relying on discipline to avoid calling setters that technically exist, as would be the case with a mutable @Data class.

What does @Value do to a class's fields by default?
Does @Value generate setter methods?

14. What is @Builder used for?

@Builder generates the builder pattern for a class — a fluent, chainable way to construct an object field by field, particularly useful for classes with many fields (especially optional ones) where a single large constructor call would be hard to read and easy to get wrong.

@Builder
public class Person {
    private String name;
    private int age;
    private String email;
}

Person p = Person.builder()
    .name("Ada")
    .age(34)
    .email("ada@example.com")
    .build();

Unlike a plain constructor call where argument order and meaning can be easy to mix up (especially with several parameters of the same type), the builder's named methods make each value's purpose explicit at the call site, and fields left unset simply default to their type's default value rather than requiring every field to be specified.

What problem does @Builder solve compared to a long constructor call?
What happens to a field left unset when using the generated builder?

15. What is @Slf4j used for?

@Slf4j generates a private, static logger field named log, pre-configured for the SLF4J logging facade, saving you from writing the same one-line logger declaration in every class that needs logging.

@Slf4j
public class OrderService {
    public void placeOrder(Order o) {
        log.info("Placing order {}", o.getId());
    }
}
//  equivalent to manually writing:
//  private static final Logger log = LoggerFactory.getLogger(OrderService.class);

Lombok provides similar logger-generating annotations for other logging frameworks too — @Log4j2, @CommonsLog, @Log (for java.util.logging) — each generating the equivalent boilerplate declaration for that specific framework's logger type, bound to the correct class automatically.

What field does @Slf4j generate?
What is the equivalent hand-written declaration @Slf4j replaces?

16. What is @NonNull used for?

Placing @NonNull on a field, constructor parameter, or method parameter tells Lombok to generate a null check at the start of the relevant generated (or annotated) method, throwing a NullPointerException immediately with a clear message if the value is null, rather than letting a null slip through and fail confusingly somewhere else later.

public class Person {
    @NonNull
    private String name;

    public Person(@NonNull String name) {
        this.name = name;
    }
}
//  generates: if (name == null) {
//      throw new NullPointerException("name is marked non-null but is null");
//  }

It's a lightweight, compile-time-generated form of defensive null checking — not a substitute for a real nullability analysis tool, but a convenient way to fail fast with a clear error at the point a null was actually passed in, rather than at some later, harder-to-trace point in the code.

What does Lombok generate when @NonNull is applied to a constructor parameter?
What is the main benefit of @NonNull's generated check?

17. What is @Cleanup used for?

@Cleanup automatically calls a resource's close() method (or another method you specify) at the end of the enclosing block, functioning as a Lombok-era predecessor to Java's built-in try-with-resources for ensuring resources get released.

@Cleanup InputStream in = new FileInputStream("data.txt");
//  Lombok inserts a finally block that calls in.close() at the end of this scope

Since Java 7 introduced try-with-resources as a language feature, @Cleanup is largely historical for closing standard AutoCloseable resources — most modern code reaches for try-with-resources directly instead. It remains occasionally useful for calling a cleanup method that isn't named close(), via @Cleanup("customMethodName"), which try-with-resources can't directly express.

What does @Cleanup ensure happens at the end of the enclosing block?
What modern Java language feature largely superseded @Cleanup for standard resources?

18. What is @SneakyThrows used for?

@SneakyThrows lets a method throw a checked exception without declaring it in a throws clause and without wrapping it in a try/catch — Lombok generates bytecode that throws the checked exception directly, exploiting the fact that the JVM itself doesn't actually enforce checked exceptions the way the Java compiler does.

@SneakyThrows
public void readFile(String path) {
    Files.readAllBytes(Paths.get(path));   //  IOException is checked, but no throws clause needed here
}

It's mainly used to avoid boilerplate try/catch-and-rethrow-as-unchecked patterns, particularly in functional interfaces (like a lambda passed to Stream.map) where checked exceptions are awkward to propagate. It's controversial precisely because it bypasses the compiler's checked-exception enforcement, which some teams consider valuable and don't want circumvented so easily.

What does @SneakyThrows let a method do?
Why is @SneakyThrows sometimes considered controversial?

19. What is @Synchronized used for?

@Synchronized is Lombok's safer alternative to Java's built-in synchronized keyword on a method: instead of locking on this (or the class object for a static method), which exposes the lock to any external code that also happens to synchronize on the same object, Lombok generates a private, dedicated lock field that only this class's own generated code touches.

@Synchronized
public void increment() {
    counter++;
}
//  generates a private final Object lock = new Object();
//  and wraps the method body in synchronized(lock) { ... }

This avoids a subtle but real risk with plain synchronized methods: since this is a public, externally-visible reference, any other code that happens to synchronize on the same instance could unintentionally interact with (or deadlock against) your class's own internal locking, something a private, dedicated lock object entirely avoids.

What does @Synchronized generate that plain `synchronized` on a method doesn't use?
Why is locking on `this` in a plain synchronized method potentially risky?

20. What is @With used for?

@With generates a "wither" method — a method that returns a new instance of the class with one field changed and every other field copied unchanged from the original, which is the standard pattern for "updating" an immutable object without mutating it.

@Value
@With
public class Point {
    int x;
    int y;
}

Point p1 = new Point(1, 2);
Point p2 = p1.withX(5);   //  new Point(5, 2); p1 itself is unchanged

It's the natural companion to @Value-style immutable classes: since you can't set a field directly on an immutable object, @With gives you a clean, named way to derive a modified copy instead, which is the idiomatic pattern for working with immutable data.

What does a generated `withX(newValue)` method return?
Why does @With pair naturally with @Value?

21. Describe how Lombok works under the hood (annotation processing)?

Lombok hooks into the Java compilation process as an annotation processor, a standard extension point the javac compiler supports for running code during compilation that can inspect and modify the program being compiled. Unlike most annotation processors, which are restricted to only generating new, separate source files, Lombok uses (officially unsupported, internal) compiler APIs to directly modify the in-memory abstract syntax tree (AST) of the class being compiled.

flowchart LR
    A[Source .java file with annotations] --> B[javac parses to AST]
    B --> C[Lombok annotation processor runs, modifies AST in place]
    C --> D[javac continues compiling the modified AST]
    D --> E[Generated .class bytecode includes Lombok's additions]

This is why Lombok can add whole new methods to an existing class (something standard annotation processors can't do) — it directly edits the parsed representation of your class before the compiler finishes turning it into bytecode, rather than generating a separate file alongside it.

What compiler extension point does Lombok use to do its work?
Why can Lombok add methods directly to an existing class, unlike most annotation processors?

22. How do you install the Lombok plugin in an IDE?

Because Lombok's generated methods don't exist in your actual source file, an IDE that only parses the source as written would show errors for calls to getters/setters that "don't exist" — the IDE plugin teaches the IDE's own code analysis to understand Lombok's annotations and treat the generated methods as if they were really there.

//  IntelliJ IDEA: Settings/Preferences -> Plugins -> search "Lombok" -> Install -> restart IDE
//  Eclipse: download lombok.jar, run `java -jar lombok.jar`, point it at your Eclipse install

Most modern IDEs (IntelliJ IDEA, Eclipse, VS Code with the Java extension pack) have dedicated Lombok support available as a plugin or built-in feature. Without it installed, the project can still compile correctly from the command line (since javac itself runs the annotation processor regardless), but the IDE's editor will show false "cannot find symbol" errors and won't offer code completion for the generated methods.

Why is an IDE plugin needed for Lombok, even though the project compiles fine from the command line?
What happens in an IDE without the Lombok plugin installed, on a Lombok-using project?

23. What are the types of Lombok annotations?

Lombok's annotations fall into two broad groups, reflected in their package names.

Core (lombok.*)Experimental (lombok.experimental.*)
Stable, well-established annotations: @Getter, @Setter, @Data, @Builder, @ToString, @EqualsAndHashCode, and similar. Newer or more situational features whose API may still change: @Accessors, @FieldDefaults, @Delegate, @UtilityClass, and others.
Safe to rely on for long-term stability. Usable, but expect more possibility of behavior/API changes across Lombok versions.

import lombok.Getter;                    //  core
import lombok.experimental.FieldDefaults; //  experimental

The distinction is mainly a signal about stability and maturity rather than a hard functional boundary — experimental annotations work fine in practice, but Lombok's maintainers reserve more freedom to change their behavior across versions than they would for the long-stable core set.

What package do Lombok's newer, more situational annotations typically live in?
What is the main practical implication of an annotation being 'experimental'?

24. How do you exclude a field from @ToString?

Annotating a specific field with @ToString.Exclude removes just that field from the generated toString() output, while every other field is still included as usual.

@ToString
public class User {
    private String username;

    @ToString.Exclude
    private String password;
}
//  toString() produces "User(username=ada)" - password is omitted

This is the standard way to keep sensitive fields (passwords, tokens, secrets) out of log output, since toString() is frequently what ends up in application logs, whether directly logged or captured incidentally through an object being passed to a logging call. It's good practice to apply this proactively to any field holding sensitive data, rather than discovering the leak after it's already appeared in production logs.

What annotation excludes a single field from the generated toString()?
Why is excluding sensitive fields from toString() good practice?

25. How do you exclude a field from @EqualsAndHashCode?

@EqualsAndHashCode.Exclude on a field removes it from both the equality comparison and the hash code calculation, while every other (non-excluded) field is still used as normal.

@EqualsAndHashCode
public class Session {
    private String userId;

    @EqualsAndHashCode.Exclude
    private long lastAccessedAt;   //  shouldn't affect whether two Session objects are "equal"
}

This matters for fields that are naturally variable or derived rather than part of the object's actual identity — a "last accessed" timestamp, a cached computed value, or a mutable collection whose changing contents shouldn't flip an object's equality (and, more subtly, whose contents changing after being placed in a HashSet could break that set's internal invariants if it were included in the hash code).

What annotation excludes a field from both equals() and hashCode()?
Why exclude a mutable, frequently-changing field like a timestamp from equality?

26. What is @Builder.Default used for?

Normally, a field's default field initializer (like private int retries = 3;) is silently ignored when using @Builder, because the builder constructs the object through its own constructor logic rather than the field's normal initialization path — without @Builder.Default, an unset field in the builder would end up as its type's zero value (0, null, etc.), not the initializer you wrote.

@Builder
public class RetryConfig {
    @Builder.Default
    private int maxRetries = 3;   //  without this annotation, unset -> 0, not 3
}

RetryConfig cfg = RetryConfig.builder().build();
//  cfg.getMaxRetries() == 3, thanks to @Builder.Default

This is a common gotcha for developers new to Lombok's builder: field initializers you'd expect to "just work" are silently dropped unless explicitly marked with @Builder.Default, so any field with a meaningful default value needs this annotation to actually preserve that default when built via the builder.

What happens to a field's normal initializer value if @Builder is used without @Builder.Default?
What does @Builder.Default fix?

27. Why does IDE support matter for using Lombok effectively?

Since Lombok's generated methods only exist after annotation processing runs during compilation, an IDE without Lombok awareness sees only the source file as literally written — no getters, no setters, no generated constructor — and will flag every call to a Lombok-generated method as an error, even though the project compiles and runs perfectly fine from the command line.

Beyond avoiding false error highlighting, proper IDE support also enables:

  • Code completion suggesting the generated methods as you type
  • "Navigate to declaration" jumping to the field/annotation responsible, since there's no literal method body to jump to
  • Refactoring tools (rename field, find usages) correctly tracking the generated methods too

Without this, working in a Lombok-heavy codebase in an unsupported editor is a genuinely frustrating experience — constant red squiggly lines for code that's actually correct — which is why the plugin is considered close to mandatory for serious day-to-day Lombok use, even though it isn't required for the build itself to succeed.

Without IDE Lombok support, what does the editor typically show for Lombok-generated method calls?
What capability does proper IDE Lombok support add beyond avoiding false errors?

28. What is the difference between @Data and @Value?

Both are convenience bundles that save you from applying several annotations individually, but they target opposite design intents: @Data produces a mutable class; @Value produces an immutable one.

@Data@Value
Fields stay mutable by default (non-final unless you mark them so). Fields become final automatically; the class itself becomes final too.
Generates getters and setters.Generates only getters, no setters.
Good fit for typical mutable POJOs, DTOs, JPA entities (with caution). Good fit for immutable value objects (coordinates, money, IDs).

@Data public class MutablePoint { private int x, y; }
@Value public class ImmutablePoint { int x, y; }

What does @Value do to the class and its fields that @Data does not?
Which annotation is the better fit for a typical mutable DTO?

29. What is the difference between @NoArgsConstructor, @AllArgsConstructor, and @RequiredArgsConstructor?

All three generate a constructor, but they differ in which fields end up as parameters.

@NoArgsConstructor@RequiredArgsConstructor@AllArgsConstructor
No parameters at all. Only final fields and @NonNull fields without a default. Every field, in declaration order.
Useful for frameworks needing a default constructor. Useful for constructor-injection style, or enforcing required values at construction. Useful for a fully-populating constructor, or as the delegate target for @Builder.

public class Person {
    private final String id;   //  required
    private String name;       //  not required
    private int age;           //  not required
}
//  @NoArgsConstructor      -> Person()
//  @RequiredArgsConstructor -> Person(String id)
//  @AllArgsConstructor      -> Person(String id, String name, int age)

It's common to combine two or three of these on the same class to support multiple valid ways of constructing it, depending on what a given caller (application code vs. a framework) actually needs.

Which constructor annotation only parameterizes final/@NonNull-without-default fields?
Which constructor annotation is commonly the delegate target for @Builder?

30. When should you use @Builder instead of a constructor?

Reach for @Builder once a class has several fields, especially a mix of required and optional ones, or multiple fields of the same type where positional constructor arguments become easy to mix up (two adjacent String parameters, for instance, where swapping them compiles fine but is wrong).

//  error-prone: which String is which?
new Person("Ada", "ada@example.com", "Engineering");

//  explicit and self-documenting
Person.builder()
    .name("Ada")
    .email("ada@example.com")
    .department("Engineering")
    .build();

A plain constructor (often via @AllArgsConstructor) remains perfectly fine for small classes with two or three unambiguous fields. The builder earns its keep specifically as field count and ambiguity grow, or when you want optional fields to have sensible defaults without needing a combinatorial explosion of overloaded constructors to cover every combination of "which fields are specified."

What problem does @Builder help avoid that a plain multi-argument constructor doesn't?
When is a plain constructor (via @AllArgsConstructor) still a perfectly fine choice?

31. How does @EqualsAndHashCode handle inheritance by default?

By default, @EqualsAndHashCode does not call the superclass's equals/ hashCode — it only considers the fields declared in the annotated class itself, which can silently produce incorrect equality if a subclass has meaningful fields inherited from a parent class that should also factor into equality.

@EqualsAndHashCode(callSuper = true)
public class Employee extends Person {
    private String department;
    //  now includes Person's fields (via Person's equals/hashCode) AND department
}

Setting callSuper = true makes the generated method also invoke the superclass's equals/hashCode and factor that result in, which is usually what you want once there's a real inheritance hierarchy with meaningful parent-class state. Lombok will actually emit a compiler warning if you extend a class and don't specify callSuper explicitly, precisely because getting this wrong is a common, easy-to-miss bug.

Does @EqualsAndHashCode call the superclass's equals/hashCode by default?
What does Lombok do if you extend a class without specifying callSuper explicitly?

32. Why can @Data be risky on JPA entity classes?

@Data bundles @EqualsAndHashCode and @ToString across every field, which interacts poorly with several JPA/Hibernate specifics: lazy-loaded associations can trigger unwanted database queries the moment toString() or equals() touches them, and including a mutable, database-generated id field in equals/hashCode can break collection behavior for entities that are added to a Set before being persisted (when the id is still null) and then persisted afterward (when it gets a real value).

@Entity
@Data   //  risky: consider a more targeted set of annotations instead
public class Order {
    @Id
    private Long id;

    @OneToMany
    private List<OrderItem> items;   //  lazy loading + toString/equals = trouble
}

A more deliberate approach on entities is usually: exclude lazy associations from @ToString/@EqualsAndHashCode, and base equality on a stable business key rather than the mutable, sometimes-null database id, rather than reaching for the all-in-one @Data bundle by default.

What can happen if @Data's generated toString() touches a lazy-loaded JPA association?
Why is including a database-generated id field in equals/hashCode risky for entities?

33. How do you combine Lombok's @Builder with inheritance?

Plain @Builder doesn't naturally handle a subclass adding its own fields on top of a parent class's builder — by default, each class's @Builder only knows about that class's own fields. Lombok's @SuperBuilder annotation exists specifically to solve this, generating a builder that's aware of, and can be extended by, subclasses.

@SuperBuilder
public class Vehicle {
    protected String make;
}

@SuperBuilder
public class Car extends Vehicle {
    private int doors;
}

Car c = Car.builder()
    .make("Toyota")   //  inherited from Vehicle's builder
    .doors(4)
    .build();

Both the parent and every subclass in the hierarchy need @SuperBuilder (not the plain @Builder) for this chaining to work correctly — mixing plain @Builder on a parent with @SuperBuilder on a child (or vice versa) doesn't work, since they generate incompatible builder implementations.

What annotation does Lombok provide specifically for builders that work across an inheritance hierarchy?
What is required for @SuperBuilder to chain correctly across a class hierarchy?

34. What is the difference between @Getter(lazy = true) and a normal @Getter?

A normal @Getter just returns the field's current value directly. @Getter(lazy = true) is meant for a field whose value is expensive to compute: it generates a getter that computes the value only the first time it's called, caches the result, and returns the cached value on every subsequent call.

public class Config {
    @Getter(lazy = true)
    private final List<String> expensiveData = computeExpensiveData();
}
//  computeExpensiveData() only actually runs the first time getExpensiveData() is called

This requires the field to be final and assigned via an initializer expression, since Lombok implements the caching using a specific double-checked-locking pattern under the hood, generating thread-safe lazy initialization without you having to hand-write that pattern yourself — a genuinely tricky pattern to get right manually.

What does @Getter(lazy = true) change about when a field's value is computed?
What is required of a field using @Getter(lazy = true)?

35. Why should you be careful using @EqualsAndHashCode on Hibernate entities with lazy-loaded proxies?

Hibernate sometimes returns a lazy-loading proxy object standing in for the real entity, rather than the actual entity instance itself — and a proxy's runtime class differs from the real entity's class (it's a dynamically generated subclass). A naive equals() comparing this.getClass() == other.getClass(), which is what @EqualsAndHashCode can generate by default in some configurations, can incorrectly report a real entity and its own proxy as unequal, purely because their runtime classes differ.

@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@Entity
public class Product {
    @Id
    @EqualsAndHashCode.Include
    private Long id;   //  compare by id, ignoring runtime class differences from proxying
}

The safer pattern is to base equality on the entity's stable identifier field alone, using onlyExplicitlyIncluded = true combined with @EqualsAndHashCode.Include on just the id, and to compare using instanceof rather than exact class equality, avoiding the proxy/class mismatch entirely.

Why can a Hibernate proxy's runtime class differ from the real entity's class?
What is a safer basis for equality on JPA entities affected by proxying?

36. What is @Accessors used for and how does it change fluent-style access?

@Accessors customizes the naming and chaining style of Lombok's generated getters/setters, moving away from strict JavaBeans conventions toward a more fluent, chainable style some codebases prefer.

@Accessors(fluent = true, chain = true)
@Getter @Setter
public class Person {
    private String name;
    private int age;
}

Person p = new Person().name("Ada").age(34);   //  no "get"/"set" prefix, and setters return `this`

fluent = true drops the get/set prefixes entirely (so name() and age() serve as both getter-style and setter-style access), while chain = true makes setters return the object itself so calls can be chained. This is a stylistic choice — standard JavaBeans-style accessors remain the default and are what most frameworks (like Jackson, by default) expect, so switching to fluent accessors can require extra configuration for such tools to still work correctly.

What does @Accessors(fluent = true) change about generated getter/setter names?
What does chain = true add to generated setters?

37. How does Lombok handle @ToString with circular references?

Lombok's @ToString doesn't have built-in circular reference detection — if class A has a @ToString-included field pointing to class B, and B has a field pointing back to A, calling toString() on either one recurses infinitely (A's toString calls B's toString, which calls A's toString, and so on) until it overflows the stack with a StackOverflowError.

@ToString
public class Parent {
    private List<Child> children;   //  danger: each Child references parent back
}

@ToString
public class Child {
    @ToString.Exclude   //  break the cycle here
    private Parent parent;
    private String name;
}

The standard fix is exactly what's shown above: use @ToString.Exclude on whichever side of the bidirectional relationship you don't need printed — typically the "back-reference" side (child pointing to parent) — so the generated toString() never actually walks the cycle in the first place.

What happens if @ToString is used on two classes that reference each other bidirectionally, unmitigated?
What is the standard fix for this circular toString problem?

38. What is @FieldDefaults used for?

@FieldDefaults (an experimental annotation) lets you set a default access level and/or final-ness for every field in a class in one line, rather than repeating private final on each field individually.

@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
public class Point {
    int x;   //  becomes: private final int x;
    int y;   //  becomes: private final int y;
}

It's a small readability/consistency tool for classes with many fields that should uniformly be private and immutable — commonly paired with @Value, which actually applies similar defaults for you already, so @FieldDefaults is more useful in classes using individual annotations like @Getter rather than the all-in-one @Value/@Data bundles that already set field visibility and finality themselves.

What does @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) do?
When is @FieldDefaults most useful compared to using @Value?

39. What is @Delegate used for?

@Delegate (experimental) generates wrapper methods on a class that forward calls to a field's methods, implementing the composition-based delegation pattern without hand-writing a pass-through method for every method you want to expose.

public class SortedList {
    @Delegate
    private final List<String> items = new ArrayList<>();
}
//  SortedList now exposes add(), remove(), size(), etc. - all forwarded to `items`
//  without SortedList itself implementing List or writing any pass-through methods

This gives you composition's flexibility (you're not locked into extending a specific class, and you can selectively expose only some of the delegate's interface) while avoiding the tedium of manually writing a forwarding method for every single method on the delegated type — particularly valuable when delegating to an interface with many methods, like List or Map.

What pattern does @Delegate implement without hand-written boilerplate?
What advantage does @Delegate give over simply extending the delegate's class?

40. Why is Lombok's approach considered a "compiler hack" and what are the implications?

Lombok works by using internal, officially unsupported compiler APIs (from javac's and Eclipse's own compiler internals) to directly rewrite a class's abstract syntax tree during compilation — something the standard, documented annotation processing API doesn't actually permit (it only supports generating entirely new, separate source files, not modifying existing ones). This is why the community often describes it as "hacking" the compiler rather than simply "using" it as intended.

The practical implications:

  • Compiler version sensitivity — a new major JDK release can change internal APIs Lombok depends on, requiring Lombok itself to be updated before it works with that JDK version.
  • Tooling friction — any tool that processes source or bytecode without knowing about Lombok (some static analyzers, certain IDE features) can behave unexpectedly around Lombok-generated code.
  • A degree of "magic" — some teams deliberately avoid Lombok specifically because they're uncomfortable relying on unofficial compiler internals for something as central as their class definitions.
Why is Lombok described as a 'compiler hack'?
What is one practical risk of Lombok's reliance on internal compiler APIs?

41. How do you debug generated Lombok code?

Since the generated methods don't appear in your source file, stepping through them in a debugger or inspecting exactly what was generated requires a couple of specific techniques rather than just reading the .java file.

#  view the actual generated code as plain Java:
delombok src/main/java -d src/delomboked

#  or inspect the compiled .class file's bytecode directly:
javap -c -p target/classes/com/example/Person.class

delombok (Lombok's own tool) converts your annotated source into an equivalent, fully expanded plain-Java source tree with no Lombok annotations at all — the clearest way to see exactly what code was generated. Most modern IDEs with Lombok plugin support also let you step into generated methods directly in the debugger, since the plugin makes the IDE aware of them, though the "source" it shows during debugging is often a synthesized view rather than a literal file on disk.

What does the delombok tool produce?
What can javap -c -p help you inspect for a Lombok-generated method?

42. What issues can Lombok cause with static code analysis tools?

Static analysis tools that operate on source code (rather than compiled bytecode) may not understand Lombok's annotations at all, and so analyze the class as if the generated methods simply don't exist — this can produce both false positives (flagging a field as "never read" when it actually is, via a Lombok-generated getter) and false negatives (missing a real bug inside logic that only exists after Lombok's generation, since the tool never sees it).

//  a source-based analyzer might flag `email` as unused,
//  not realizing @Getter/@Setter generate real accessors for it
@Getter @Setter
private String email;

The fix generally depends on the specific tool: many mainstream static analysis tools (SonarQube, Checkstyle, PMD, certain IDE inspections) have added explicit Lombok awareness over time, either natively or via a plugin, so this has become less of a problem than it once was — but a lesser-known or in-house tool that only parses raw source may still need this pointed out or configured for, and coverage tools measuring line coverage can also misreport results for Lombok-generated code that technically has no corresponding source lines to mark as covered.

Why might a source-based static analysis tool flag a Lombok-annotated field as 'unused'?
What has generally improved this situation over time?

43. What are the risks of using @Data with mutable collections?

A field like private List<String> tags; under @Data gets a plain getter that returns the actual internal list reference, not a defensive copy — meaning external code that calls the getter can mutate the object's internal state directly, bypassing any invariant the class was supposed to maintain.

@Data
public class Team {
    private List<String> members;
}

Team t = new Team(List.of("Ada"));
t.getMembers().add("Grace");   //  mutates Team's internal state from outside, if the list itself is mutable

This also interacts badly with @EqualsAndHashCode and HashSet/HashMap usage: if such an object is placed in a hash-based collection and then its internal collection field is mutated externally (changing its hash code), the object can become "lost" in that collection, unable to be found by a subsequent lookup even though it's still physically present. Defensive copying in the getter, or exposing an unmodifiable view, is something you'd have to add manually — Lombok doesn't do it for you.

What does a plain Lombok @Getter return for a mutable collection field?
What problem can arise if such an object is in a HashSet and its collection field is then mutated?

44. How does delombok work and when would you use it?

delombok runs Lombok's own transformation logic but, instead of feeding the result to the compiler, writes out the fully expanded, plain-Java equivalent source code — every getter, setter, constructor, and other generated method spelled out explicitly, with no Lombok annotations remaining.

java -jar lombok.jar delombok src/main/java -d src/main/delomboked

Common reasons to use it:

  • Generating Javadoc — the standard Javadoc tool doesn't understand Lombok annotations, so running delombok first produces source Javadoc can actually process and document correctly.
  • Debugging exactly what Lombok generated, without guessing from the annotation alone.
  • Removing a Lombok dependency from a project — delomboking the whole codebase gives you a starting point of equivalent plain Java to work from, rather than hand-writing every generated method from scratch.
What does delombok produce?
Why is delombok often run before generating Javadoc?

45. Why might Lombok cause issues in multi-module Maven builds with annotation processor ordering?

Annotation processors run in whatever order the build tool discovers and applies them, and if a project uses multiple processors together (Lombok alongside MapStruct, or a custom code-generating processor), the order they run in can matter — particularly since Lombok's generated methods need to already exist by the time another processor that depends on seeing them runs its own analysis.

<annotationProcessorPaths>
    <path>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </path>
    <path>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct-processor</artifactId>
    </path>
</annotationProcessorPaths>

Explicitly declaring annotationProcessorPaths (rather than relying on whatever processors happen to be found on the classpath) lets you control this ordering deliberately, which becomes especially relevant in multi-module builds where different modules might otherwise pick up processors in inconsistent orders depending on classpath assembly quirks, leading to intermittent, hard-to-reproduce build failures where a processor can't find methods Lombok hadn't generated yet.

Why can annotation processor ordering matter when Lombok is combined with another processor like MapStruct?
What Maven configuration lets you control annotation processor ordering explicitly?

46. What is @UtilityClass used for?

@UtilityClass (experimental) converts a class into a static utility class: it makes the class final, generates a private constructor that throws if somehow invoked via reflection, and marks every field and method in the class as static automatically — the standard boilerplate for a class that's meant to be a bag of static helper methods, never instantiated.

@UtilityClass
public class MathUtils {
    public int square(int x) {
        return x * x;
    }
}
//  equivalent to a final class with a private constructor
//  and `square` implicitly treated as `public static int square(int x)`

Without it, writing a proper utility class by hand means remembering to mark the class final, add a private no-op constructor, and mark every single method static individually — small but easy-to-forget details that @UtilityClass handles for you in one line.

What does @UtilityClass automatically do to methods declared in the class?
What does @UtilityClass generate to prevent instantiation?

47. What is the difference between Lombok's @Builder and the traditional Gang-of-Four Builder pattern?

The classic Gang-of-Four Builder pattern is typically hand-written as a separate, standalone class (often with its own interface) explicitly responsible for assembling a complex object step by step, sometimes supporting different construction algorithms or representations via a "Director" that orchestrates the builder. Lombok's @Builder automatically generates a much simpler, single-purpose builder scoped to just the annotated class, focused purely on the "fluent field-by-field construction" convenience rather than the full flexibility of the original pattern.

GoF Builder patternLombok @Builder
Hand-written, can support multiple representations/directors. Auto-generated, tied directly to one class's fields.
More ceremony, more flexibility for complex construction logic. Minimal ceremony, covers the common "set fields fluently" case well.

In practice, most real-world uses of "the builder pattern" in application code are really just this simpler, fluent-construction convenience — which is exactly what Lombok automates — rather than the full GoF pattern with its director/strategy flexibility, which is comparatively rare to need in ordinary business code.

What does Lombok's @Builder focus on, compared to the full GoF Builder pattern?
How much of real-world 'builder pattern' usage does Lombok's simpler version typically cover?

48. How do you use Lombok with Java records - is Lombok still needed?

Java's built-in record type (since Java 16) already generates a canonical constructor, accessors, equals(), hashCode(), and toString() automatically — covering a large chunk of what @Value or @Data used to provide for immutable classes, without needing Lombok at all.

//  Java record - no Lombok needed for this much:
public record Point(int x, int y) {}
//  automatically gets: constructor, x(), y(), equals(), hashCode(), toString()

Lombok still adds value on top of records for things records don't provide natively — most notably @Builder, since records don't have a built-in fluent builder, and @With-style "derive a modified copy" methods, which records also lack built-in. So for simple immutable data carriers, records alone are often enough; Lombok remains useful specifically when you want a builder or wither-style methods on top of a record, or for classes that don't fit the record shape (mutable classes, classes needing custom equals/hashCode logic beyond field-based defaults).

What does a Java record already generate automatically, without Lombok?
What does Lombok still add value for, even when using records?
«
»

Comments & Discussions