Java Lombok Core Annotations

0. Introduction
When working with Java DTOs, entities, or simple data holder classes, developers often write a large amount of repetitive boilerplate code. Common examples include getters, setters, equals(), hashCode(), and toString() methods.
Project Lombok is a popular library that reduces this boilerplate by generating code at compile time using annotations.
However, Lombok annotations are often used without fully understanding what code they generate, especially since several of them provide overlapping functionality.
This article explains five commonly used Lombok annotations that frequently appear in Java projects:
@Getter@Setter@ToString@EqualsAndHashCode@Data
Understanding these annotations helps developers write cleaner DTO classes while still being aware of what code Lombok actually generates.
1. @Getter
The @Getter annotation generates getter methods for fields.
1.1. Example
import lombok.Getter;
@Getter
class Person {
private final UUID id;
private String name;
private int age;
}
Lombok automatically generates:
public UUID getId() {
return this.id;
}
public String getName() {
return this.name;
}
public int getAge() {
return this.age;
}
1.2. Usage Notes
Can be applied to a class or to individual fields
Commonly used for DTOs and immutable objects
Example with field-level annotation:
@Getter
private String name;
2. @Setter
The @Setter annotation generates setter methods for fields.
2.1. Example
import lombok.Setter;
@Setter
class Person {
private final UUID id;
private String name;
private int age;
}
Generated methods:
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
Lombok does not generate setters for final fields because setters would require reassigning the field, which is prohibited by the final modifier.
2.2. Usage Notes
Can be applied to a class or individual fields
Often avoided in immutable classes
3. @ToString
The @ToString annotation generates a toString() method automatically.
3.1. Example
import lombok.ToString;
@ToString
class Person {
private final UUID id;
private String name;
private int age;
}
Generated method conceptually looks like:
public String toString() {
UUID var10000 = this.getId();
return "Person(id=" + var10000 + ", name=" + this.getName() + ", age=" + this.getAge() + ")";
}
3.2. Usage Notes
Useful for debugging and logging.
Fields can also be excluded:
@ToString(exclude = "password")
4. @EqualsAndHashCode
The @EqualsAndHashCode annotation generates both equals() and hashCode() methods.
4.1. Why This Matters
Java’s default equals() method compares object references, meaning it only returns true when two references point to the same object.
In practice, we often override equals() to compare objects based on their field values instead. When doing so, we must also override hashCode() to maintain the Java contract: equal objects must produce the same hash code.
4.2. Example
import lombok.EqualsAndHashCode;
@EqualsAndHashCode
class Person {
private final UUID id;
private String name;
private int age;
}
Lombok generates implementations like:
public boolean equals(Object o) {
if (o == this) {
return true;
} else if (!(o instanceof Person)) {
return false;
} else {
Person other = (Person)o;
if (!other.canEqual(this)) {
return false;
} else if (this.getAge() != other.getAge()) {
return false;
} else {
Object this$id = this.getId();
Object other$id = other.getId();
if (this$id == null) {
if (other$id != null) {
return false;
}
} else if (!this\(id.equals(other\)id)) {
return false;
}
Object this$name = this.getName();
Object other$name = other.getName();
if (this$name == null) {
if (other$name != null) {
return false;
}
} else if (!this\(name.equals(other\)name)) {
return false;
}
return true;
}
}
}
protected boolean canEqual(Object other) {
return other instanceof Person;
}
public int hashCode() {
int PRIME = 59;
int result = 1;
result = result * 59 + this.getAge();
Object $id = this.getId();
result = result * 59 + (\(id == null ? 43 : \)id.hashCode());
Object $name = this.getName();
result = result * 59 + (\(name == null ? 43 : \)name.hashCode());
return result;
}
4.3. Things to be aware of
In general, we should avoid using Lombok to generate the equals() and hashCode() methods for our JPA entities.
By default, Lombok generates equals() and hashCode() using all fields of the class. However, JPA entities often rely on a database identifier (@Id) to define identity. Before the entity is persisted, the identifier may be null, which can lead to inconsistent equality behavior.
In addition, including relationships or mutable fields in equals() and hashCode() can cause unexpected issues such as incorrect comparisons or performance problems.
For this reason, many JPA best practices recommend implementing equals() and hashCode() manually and basing them only on the entity’s identifier.
5. @Data
The @Data annotation is a shortcut annotation that bundles several Lombok features together.
5.1. What @Data Includes
@Data automatically generates:
@Getter@Setter@ToString@EqualsAndHashCode@RequiredArgsConstructor
5.2. Example
import lombok.Data;
@Data
class Person {
private final UUID id;
private String name;
private int age;
}
Using @Data is convenient for simple DTO classes where all fields need standard methods.
Generated methods:
public class Person {
private final UUID id;
private String name;
private int age;
public Person(UUID id) {
this.id = id;
}
public UUID getId() {
return this.id;
}
public String getName() {
return this.name;
}
public int getAge() {
return this.age;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public boolean equals(Object o) {
if (o == this) {
return true;
} else if (!(o instanceof Person)) {
return false;
} else {
Person other = (Person)o;
if (!other.canEqual(this)) {
return false;
} else if (this.getAge() != other.getAge()) {
return false;
} else {
Object this$id = this.getId();
Object other$id = other.getId();
if (this$id == null) {
if (other$id != null) {
return false;
}
} else if (!this\(id.equals(other\)id)) {
return false;
}
Object this$name = this.getName();
Object other$name = other.getName();
if (this$name == null) {
if (other$name != null) {
return false;
}
} else if (!this\(name.equals(other\)name)) {
return false;
}
return true;
}
}
}
protected boolean canEqual(Object other) {
return other instanceof Person;
}
public int hashCode() {
int PRIME = 59;
int result = 1;
result = result * 59 + this.getAge();
Object $id = this.getId();
result = result * 59 + (\(id == null ? 43 : \)id.hashCode());
Object $name = this.getName();
result = result * 59 + (\(name == null ? 43 : \)name.hashCode());
return result;
}
public String toString() {
UUID var10000 = this.getId();
return "Person(id=" + var10000 + ", name=" + this.getName() + ", age=" + this.getAge() + ")";
}
}
5.3. Things to be aware of
The inclusion of @RequiredArgsConstructor is important to understand. This annotation generates a constructor that accepts all required fields, which are:
finalfieldsfields annotated with
@NonNull
For example, if a class contains a final field, Lombok generates a constructor that requires that field as a parameter.
public Person(UUID id) {
this.id = id;
}
Because a constructor is now explicitly generated, the default no-argument constructor may no longer exist.
This can become important when working with frameworks such as JPA, Jackson, or Spring, which sometimes require a no-argument constructor. In such cases, developers often explicitly add @NoArgsConstructor alongside @Data.
6. When to Use Lombok Carefully
Although Lombok reduces boilerplate, it should be used thoughtfully.
Potential considerations include:
Generated methods are not visible in the source code
Debugging sometimes requires checking generated bytecode
Overusing
@Datain domain models can expose unwanted setters
For example, immutable objects often prefer:
@Getter
@EqualsAndHashCode
instead of @Data.
✨ Conclusion
Lombok helps reduce repetitive Java boilerplate by generating common methods automatically. Understanding what each annotation generates is important so that developers remain aware of the actual behavior of their classes. The most commonly used Lombok annotations include @Getter, @Setter, @ToString, @EqualsAndHashCode, and the convenience annotation @Data.





