1. Overview

In this tutorial, we'll look at the Lombok @EqualsAndHashCode annotation to generate the equals and hashCode methods automatically.

2. Maven Dependency

Let's first add the Lombok maven dependency:

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.8</version>
</dependency>

3. Usage of @EqualsAndHashCode

When we annotate a class with @EqualsAndHashCode, Lombok generates the equals() and hashCode() methods for us:

@EqualsAndHashCode
public class Employee {

    private String name;
    private int salary;
}

Here, we're declaring the Employee class which has 2 fields - name and salary. 

By default, Lombok uses all non-static and non-transient fields when generating equals and hashCode - in our case name and salary:

public class EmployeeDelomboked {

    private String name;
    private int salary;

    public boolean equals(Object o) {
        if (o == this) {
            return true;
        }
        if (!(o instanceof EmployeeDelomboked)) {
            return false;
        }
        final EmployeeDelomboked other = (EmployeeDelomboked) o;
        if (!other.canEqual((Object) this)) {
            return false;
        }
        final Object this$name = this.name;
        final Object other$name = other.name;
        if (this$name == null ? other$name != null : !this$name.equals(other$name)) {
            return false;
        }
        if (this.salary != other.salary) {
            return false;
        }
        return true;
    }

    public int hashCode() {
        final int PRIME = 59;
        int result = 1;
        final Object $name = this.name;
        result = result * PRIME + ($name == null ? 43 : $name.hashCode());
        result = result * PRIME + this.salary;
        return result;
    }

    protected boolean canEqual(Object other) {
        return other instanceof EmployeeDelomboked;
    }
}

Here we see what Lombok generates for us. Besides equals and hashCode, Lombok also adds the canEqual method.

4. Exclude Fields

Lombok provides several ways to exclude specific fields for the method generation.

4.1. Exclude at Field Level with @EqualsAndHashCode.Exclude

The @EqualsAndHashCode.Exclude annotation marks a field so that Lombok doesn't use that field when generating equals and hashCode:

@EqualsAndHashCode
public class Employee {

    private String name;
    @EqualsAndHashCode.Exclude
    private int age;
    @EqualsAndHashCode.Exclude
    private int salary;
}

Here, we're annotating the age and salary fields with @EqualsAndHashCode.Exclude.

As a result, Lombok excludes those fields:

public int hashCode() {
    final int PRIME = 59;
    int result = 1;
    final Object $name = this.name;
    result = result * PRIME + ($name == null ? 43 : $name.hashCode());
    return result;
}

// eqauls implementation

The generated hashCode implementation just relies on the name field.

4.2. Exclude at Class Level

We can also use the exclude attribute of @EqualsAndHashCode to exclude some fields:

@EqualsAndHashCode(exclude = {"age", "salary"})
public class Employee {

    private String name;
    private int age;
    private int salary;
}

In contrast to the previous example, we aren't annotating any fields. Instead, we're defining the excluded fields in the exclude attribute on the class level. Though, the resulting methods will be similar.

5. Include Fields

Now that we've seen how to exclude some fields, we'll now look at how we can include some fields for the generation of equals and hashCode.

5.1. Include at Field Level

The @EqualsAndHashCode.Include annotation enables us to include a specific field. We generally use this annotation while onlyExplicitlyIncluded is set to true on the @EqualsAndHashCode annotation:

@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class Employee {

    @EqualsAndHashCode.Include
    private String name;
    @EqualsAndHashCode.Include
    private int age;
    private int salary;
}

Here, the equals and hashCode methods will only use the name and age fields thus excluding the salary field. This is because we aren't annotating salary with @EqualsAndHashCode.Include and onlyExplicitlyIncluded is true.

5.2. Include at Method Level

We can also annotate methods with @EqualsAndHashCode.Include.

This way we can use the results of custom methods in the generation of equals and hashCode.

@EqualsAndHashCode
public class Employee {

    private String name;
    private int age;
    private int salary;

    @EqualsAndHashCode.Include
    public boolean canDrink() {
        return age > 18;
    }
}

Here, we have the canDrink method that is annotated with @EqualsAndHashCode.Include. Note that it isn't a JavaBean style getter method and doesn't have a backing property. Nonetheless, Lombok will use it for the method generation because of @EqualsAndHashCode.Include.

5.3. Include at Class Level

@EqualsAndHashCode also includes an attribute to define the included fields. In this case, we won't use any annotations in the fields or methods:

@EqualsAndHashCode(of = {"name", "age"})
public class Employee {

    private String name;
    private int age;
    private int salary;
}

Here,  we're listing name and age in the of attribute. So Lombok will only use those fields for equals and hashCode.

Note that this approach is very similar to the usage of @EqualsAndHashCode.Include with onlyExplicitlyIncluded. In fact, Lombok recommends the latter since the of attribute will be deprecated in the future.

6. Inheritance and Call Super Class

If the target class is extending another class, @EqualsAndHashCode generated methods don't call the parent methods.

Though, we can make Lombok also call the parent methods, by setting callSuper:

@EqualsAndHashCode(callSuper = true)
public class Employee extends Citizen {

    private String name;
    private int salary;
}

Here, Employee extends Citizen. Then we're making the equals and hashCode methods to call their parent counterparts.

public int hashCode() {
    final int PRIME = 59;
    int result = 1;
    result = result * PRIME + super.hashCode();
    final Object $name = this.name;
    result = result * PRIME + ($name == null ? 43 : $name.hashCode());
    result = result * PRIME + this.salary;
    return result;
}

// equals implementation...

We have the super.hashCode() call in the generated method.

7. Configure @EqualsAndHashCode

Now, we'll look at the configuration keys for @EqualsAndHashCode.

As the first step, we must have a lombok.config file in our project. Then we must add the configuration keys.

7.1. lombok.equalsAndHashCode.doNotUseGetters

If our class has a getter method for a field, Lombok favors the getter during the method generation. The lombok.equalsAndHashCode.doNotUseGetters configuration key disables that behavior so that Lombok accesses the fields directly.

By default, its value is false.

# [true | false] (default: false)
lombok.equalsAndHashCode.doNotUseGetters = true

Here, we're changing the value as true.

7.2. lombok.equalsAndHashCode.callSuper

In the case that our class extends another class, Lombok won't call the superclass methods. Previously, we configured this behavior per class using the @EqualsAndHashCode annotation. To configure it globally, we can use the lombok.equalsAndHashCode.callSuper configuration key:

# [call | skip | warn] (default: warn)
lombok.equalsAndHashCode.callSuper = call

The default value is warn meaning that Lombok will log a warning message. Instead, we're setting it as call to enable calling the superclass.

7.3. lombok.equalsAndHashCode.flagUsage

If we want to prevent the usage of @EqualsAndHashCode, we can use the configuration key lombok.equalsAndHashCode.flagUsage:

# [warning | error] (default: not set)
lombok.equalsAndHashCode.flagUsage = error

There is no default value for this key. Here we're setting it as error. So when Lombok detects the usage of @EqualsAndHashCode, it logs an error message like "Use of @EqualsAndHashCode is flagged according to lombok configuration".

8. Common Issues

8.1. Break Recursion

Firstly, we'll see how to break recursion which can be caused by @EqualsAndHashCode.

Similar to the @ToString annotation, bidirectional relationships between classes may result in java.lang.StackOverflowError, if both classes are using @EqualsAndHashCode:

@Getter
@Setter
@EqualsAndHashCode
public class Employee {

    private String name;
    private int salary;
    private Manager manager;
}

@Getter
@Setter
@EqualsAndHashCode
public class Manager {

    private String name;
    private Employee subordinate;
}

We have the Employee and Manager classes. Note that each class has a reference to the other. When we call hashCode() of Employee, it'll give java.lang.StackOverflowError. Because each class calls other's hashCode() method.

To solve this problem, we must exclude the manager field from the hashCode() calculation.

@Getter
@Setter
@EqualsAndHashCode(exclude = "manager")
public class Employee {

    private String name;
    private int salary;
    private Manager manager;
}

9. Conclusion

In this tutorial, we've looked at how we can use the Lombok @EqualsAndHashCode annotation to the generate equals and hashCode methods

Finally, check out the source code for all examples in this tutorial over on Github.