Issue with Lombok
Bi-directional relationships
When you have bi-directional associations in your entities, @Data can cause infinite recursion in equals(), hashCode(), or toString() methods, leading to a StackOverflowError.
Example
You have two entities with bi-directional relationship Department and Employee. And you have the below two classes.
@Data
public class Department {
private Long id;
private String name;
private List<Employee> employees; // Bi-directional relationship
}
@Data
public class Employee {
private Long id;
private String name;
private Department department; // Bi-directional relationship
}Lombok generated code
When you use Lombok’s @Data, it automatically generates an equals() and hashCode() method that includes all fields.
Let’s take the Department class as example. Lombok will generate a equals() for it behind the scene:
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Department that = (Department) o;
return Objects.equals(id, that.id) &&
Objects.equals(name, that.name) &&
Objects.equals(employees, that.employees);
}What would happen next?
When your application executes the below code:
department1.equals(department2);It is going to hit this line:
Objects.equals(employees, that.employees)That triggers equals() on each Employee. Each Employee has a reference back to the Department.
So the Employee.equals() compares its department, which again compares employees, and so on. Ultimately result in a StackOverflowError.
Solution
Other than use @Data which generates many different Lombok annotations. You can use the ones that you need (e.g. @Getter, @Setter, @NoArgsConstructor) and manually implement the equals(), toString() and hashCode() methods if you need them.
The @Builder annotation
Problem 1: Builder ignores initialised collections
Suppose you have the class:
@Getter
@Setter
@Builder
public class User {
private String name;
// Initialised field
private List<Event> attendingEvents = new ArrayList<>();
}Here @Builder does not call the field initialiser.
If you call the object with:
User user = User.builder().name("Jason").build();Then user.attendingEvents will be null, not an empty list, even though you initialised it inline.
Solution
Use Lombok’s @Builder.Default to force initialisation inside the builder.
@Builder
public class User {
private String name;
@Builder.Default
private List<Event> attendingEvents = new ArrayList<>();
}This makes the builder respect the default value.
Problem 2: Conflicts with JPA annotations
JPA entities often use:
@CreatedDate@LastModifiedDate@ManyToMany,@OneToMany, etc.
These rely on JPA’s proxy mechanism and lifecycle hooks. But @Builder:
- Bypasses constructors
- May cause unexpected nulls or incorrect lifecycle behaviour if combined with things like auditing annotations (
@CreatedDate, etc.) - Can mess with lazy loading if collections or related entities are initialised improperly
Solution
No good solution as far but avoid using @Builder in JPA entities.
Back to parent page: Java