Guide: Using Lombok with Ebean Entity Beans
April 16, 2026 · View on GitHub
Purpose
This guide explains which Lombok annotations are safe and recommended for Ebean entity beans, which ones to avoid, and why. It is written as prescriptive instructions for AI agents and developers.
The Core Rule
Do NOT use
@Dataon Ebean entity beans.
Use @Getter + @Setter instead, with the optional @Accessors(chain = true) for
a fluent setter style.
Why @Data is Incompatible with Ebean
@Data is a convenience annotation that is equivalent to applying @Getter,
@Setter, @RequiredArgsConstructor, @ToString, and @EqualsAndHashCode together.
Three of those are problematic for Ebean entity beans:
1. @EqualsAndHashCode (included in @Data) — breaks entity identity
@Data generates hashCode() and equals() based on all non-static, non-transient
fields. Ebean entity beans have identity semantics — two references to the same database
row should be considered equal based on their @Id value, not field-by-field comparison.
Problems caused:
- Inconsistent
hashCodebefore and after persist (the@Idfield is0on a new entity, then changes after insert — violating thehashCodecontract for collections) - Entities placed in a
SetorHashMapbefore saving will be unfindable after saving - Ebean's internal identity map and dirty checking can be confused
2. @ToString (included in @Data) — triggers unexpected lazy loading
@Data generates a toString() that accesses all fields, including
@OneToMany and @ManyToOne associations. Accessing an unloaded lazy association
outside of a transaction triggers a LazyInitialisationException or fires an unexpected
SQL query, which can:
- Cause subtle bugs in logging statements
- Trigger N+1 queries in test output or debug logging
- Fail with an exception if no active transaction exists
3. @RequiredArgsConstructor (included in @Data) — unnecessary for Ebean
Ebean does not require a default constructor — it can construct entity instances without
one. @RequiredArgsConstructor therefore adds nothing useful to entity beans.
Recommended Annotation Set
Use exactly these three Lombok annotations on every Ebean entity bean:
@Entity
@Getter
@Setter
@Accessors(chain = true)
@Table(name = "my_table")
public class MyEntity {
// ...
}
| Annotation | Purpose |
|---|---|
@Getter | Generates getFoo() / isFoo() accessor methods |
@Setter | Generates setFoo(value) mutator methods; Ebean enhancement intercepts these for dirty tracking |
@Accessors(chain = true) | Makes setters return this, enabling fluent/builder-style property setting |
@Accessors(chain = true) — Fluent Setter Style
With chain = true, setters return this instead of void, allowing method chaining:
// without chain = true (void setters)
CMachine machine = new CMachine();
machine.setMake("Toyota");
machine.setModel("Hilux");
machine.setStatus("active");
// with @Accessors(chain = true)
CMachine machine = new CMachine()
.setMake("Toyota")
.setModel("Hilux")
.setStatus("active");
This is particularly useful when building test data:
CMachine machine = new CMachine()
.setGid(UUID.randomUUID())
.setMachineType("HV")
.setStatus("active")
.setMake("Komatsu")
.setModel("PC200");
database.save(machine);
Ebean's bytecode enhancement is fully compatible with chained setters — the
enhancement intercepts each setFoo() call to record which fields have been modified
(dirty checking), regardless of whether the setter returns void or this.
@Accessors(fluent = true) — also compatible
@Accessors(fluent = true) removes the get/set/is prefix, generating name()
(getter) and name(value) (setter) instead of getName() and setName(value).
Ebean does not require JavaBeans naming conventions — it can work with any accessor
method style, including fluent accessors with no prefix. @Accessors(fluent = true) is
therefore compatible with Ebean.
@Accessors(chain = true) is the more common choice in practice (it keeps the familiar
get/set prefix while adding method chaining), but fluent = true is a valid
alternative if that style is preferred consistently across the codebase.
Full Entity Bean Example
package com.example.repository.data;
import io.ebean.annotation.WhenCreated;
import io.ebean.annotation.WhenModified;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import java.time.Instant;
import java.util.List;
import java.util.UUID;
@Entity
@Getter
@Setter
@Accessors(chain = true)
@Table(name = "machine")
public class CMachine {
@Id
private long id;
@Version
private int version;
@Column(nullable = false, unique = true)
private UUID gid;
@Column(nullable = false, length = 10)
private String machineType;
@Column(length = 200)
private String make;
@Column(length = 200)
private String model;
@WhenCreated
private Instant created;
@WhenModified
private Instant lastModified;
}
Summary: Lombok Annotations and Ebean Compatibility
| Lombok Annotation | Compatible? | Notes |
|---|---|---|
@Getter | ✅ Safe | Use on every entity bean |
@Setter | ✅ Safe | Use on every entity bean; enhancement intercepts these |
@Accessors(chain = true) | ✅ Safe | Recommended for fluent construction style |
@ToString | ❌ Avoid | Ebean does a better job and handles recursion |
@EqualsAndHashCode | ❌ Avoid | Breaks entity identity and @Id-based equality |
@Data | ❌ Avoid | Includes @EqualsAndHashCode and @ToString — both problematic |
@Value | ❌ Avoid | Makes fields final — incompatible with Ebean's field-level bytecode enhancement |
@Accessors(fluent = true) | ✅ Safe | Removes get/set prefix — Ebean does not require JavaBeans naming conventions and works with any accessor style |
@Builder | ⚠️ Careful | Usable on non-entity helper/factory classes; on entity beans it requires a no-arg constructor alongside it and offers no advantage over @Accessors(chain = true) |
Relationship with Ebean Bytecode Enhancement
Ebean's bytecode enhancement (applied by ebean-maven-plugin at build time) modifies
the setXxx() methods of entity beans to:
- Mark the field as dirty (changed) so only modified fields are included in UPDATE statements
- Support lazy loading of associations when a getter is called on an unloaded field
For this to work correctly, Ebean needs:
- Accessor methods for each persistent field (any naming style is fine —
getFoo(),foo(), or no accessors at all; Ebean can also access fields directly) - No override of
hashCode()/equals()that would interfere with the identity map — which means no@Dataor@EqualsAndHashCode