Java library for verifying the contract of equals and hashCode methods in unit tests
—
Comprehensive warning system for suppressing specific validation rules when they don't apply to particular use cases. EqualsVerifier provides 17 different warning types that can be suppressed individually or in combination to handle edge cases and specific design patterns.
The complete set of warnings that can be suppressed in EqualsVerifier.
/**
* Enum of warnings that can be suppressed in EqualsVerifier.
* @see nl.jqno.equalsverifier.api.EqualsVerifierApi#suppress(Warning...)
*/
public enum Warning {
ALL_FIELDS_SHOULD_BE_USED,
ALL_NONFINAL_FIELDS_SHOULD_BE_USED,
REFERENCE_EQUALITY,
IDENTICAL_COPY,
IDENTICAL_COPY_FOR_VERSIONED_ENTITY,
INHERITED_DIRECTLY_FROM_OBJECT,
NO_EXAMPLE_FOR_CACHED_HASHCODE,
NONFINAL_FIELDS,
NULL_FIELDS,
STRICT_HASHCODE,
STRICT_INHERITANCE,
SURROGATE_KEY,
SURROGATE_OR_BUSINESS_KEY,
JPA_GETTER,
TRANSIENT_FIELDS,
BIGDECIMAL_EQUALITY,
@Deprecated ZERO_FIELDS
}Warnings related to how fields are used in equals and hashCode methods.
/**
* Signals that not all fields are relevant in the equals contract. EqualsVerifier
* will not fail if one or more fields do not affect the outcome of equals.
*
* Only applies to non-transient fields.
*/
ALL_FIELDS_SHOULD_BE_USEDUsage Examples:
// Class where only some fields are used in equals
public class Person {
private String firstName;
private String lastName;
private Date birthDate;
private String socialSecurityNumber; // Not used in equals
@Override
public boolean equals(Object obj) {
// Only compares firstName, lastName, birthDate
// socialSecurityNumber is ignored
}
}
EqualsVerifier.forClass(Person.class)
.suppress(Warning.ALL_FIELDS_SHOULD_BE_USED)
.verify();/**
* Signals that non-final fields are not relevant in the equals contract. EqualsVerifier
* will not fail if one or more non-final fields do not affect the outcome of equals.
*
* Only applies to non-transient fields.
*/
ALL_NONFINAL_FIELDS_SHOULD_BE_USEDUsage Examples:
// Class where non-final fields are not part of equals
public class ImmutablePerson {
private final String name;
private final int age;
private String nickname; // Non-final, not used in equals
@Override
public boolean equals(Object obj) {
// Only compares final fields: name and age
}
}
EqualsVerifier.forClass(ImmutablePerson.class)
.suppress(Warning.ALL_NONFINAL_FIELDS_SHOULD_BE_USED)
.verify();/**
* Disables checks for non-final fields on which equals and hashCode depend.
*
* EqualsVerifier's standard behaviour is to disallow non-final fields being used in
* equals and hashCode methods, since classes that depend on non-final fields in
* these methods cannot reliably be used in collections.
*
* However, sometimes an external library requires that fields be non-final. An example of
* this are Java Beans. In such a case, suppress this warning to prevent EqualsVerifier
* from checking for non-final fields.
*/
NONFINAL_FIELDSUsage Examples:
// JavaBean with non-final fields used in equals
public class PersonBean {
private String name; // Non-final but used in equals
private int age; // Non-final but used in equals
// Getters and setters...
@Override
public boolean equals(Object obj) {
// Uses non-final fields
}
}
EqualsVerifier.forClass(PersonBean.class)
.suppress(Warning.NONFINAL_FIELDS)
.verify();Warnings related to the fundamental equality contract.
/**
* Disables the check for reference equality on fields.
*
* EqualsVerifier will check if the equals method calls equals on the object fields
* of the class under test, instead of the == operator, since normally this signifies a
* mistake (e.g. comparing string fields with ==).
*
* However, sometimes == is used intentionally, or the field in question doesn't
* implement equals itself, so a call to the equals method of that field is
* essentially a reference equality check instead of a value equality check. In these cases,
* this warning can be suppressed.
*/
REFERENCE_EQUALITYUsage Examples:
// Class that intentionally uses == for object comparison
public class Node {
private Node parent;
private String data;
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Node)) return false;
Node other = (Node) obj;
return this.parent == other.parent && // Intentional reference equality
Objects.equals(this.data, other.data);
}
}
EqualsVerifier.forClass(Node.class)
.suppress(Warning.REFERENCE_EQUALITY)
.verify();/**
* Disables the check, when the equals method is overridden in the class under test,
* that an instance of this class should be equal to an identical copy of itself.
*
* Normally, it is important that an object be equal to an identical copy of itself: after
* all, this is the point of overriding equals in the first place.
*
* However, when the class is part of a hierarchy, but should be (pseudo-)singleton, it can
* be useful to suppress this warning. This can happen in certain implementations of the Null
* Object Pattern, for example.
*
* If this warning is suppressed, and it turns out that an instance of the class under test
* is equal to an identical copy of itself after all, EqualsVerifier will fail.
*/
IDENTICAL_COPYUsage Examples:
// Singleton or Null Object Pattern implementation
public class NullPerson extends Person {
private static final NullPerson INSTANCE = new NullPerson();
public static NullPerson getInstance() {
return INSTANCE;
}
@Override
public boolean equals(Object obj) {
// Only equal to the same singleton instance
return this == obj;
}
}
EqualsVerifier.forClass(NullPerson.class)
.suppress(Warning.IDENTICAL_COPY)
.verify();/**
* Disables the check, when the equals method is overridden in the class under test,
* that an instance of this class should be equal to an identical copy of itself.
*
* Normally, it is important that an object be equal to an identical copy of itself: after
* all, this is the point of overriding equals in the first place.
*
* However, when the class is a kind of versioned entity and there is an id field
* that is zero when the object is new, it is often the case that two new objects are never
* equal to each other. In these cases, it can be useful to suppress this warning.
*
* You cannot use IDENTICAL_COPY in these cases, because when the ids are
* equal, the objects should be, too, and EqualsVerifier would fail in this case.
*
* If this warning is suppressed, and it turns out that an instance of the class under test
* is equal to an identical copy of itself after all, EqualsVerifier will NOT fail.
*/
IDENTICAL_COPY_FOR_VERSIONED_ENTITYUsage Examples:
// Entity with auto-generated ID that affects equals
public class VersionedEntity {
private Long id; // 0 or null for new entities
private String name;
@Override
public boolean equals(Object obj) {
if (!(obj instanceof VersionedEntity)) return false;
VersionedEntity other = (VersionedEntity) obj;
// New entities (id == null) are never equal
if (this.id == null || other.id == null) return false;
return Objects.equals(this.id, other.id);
}
}
EqualsVerifier.forClass(VersionedEntity.class)
.suppress(Warning.IDENTICAL_COPY_FOR_VERSIONED_ENTITY)
.verify();Warnings related to inheritance hierarchies and method overriding.
/**
* Disables some of the stricter inheritance tests.
*
* EqualsVerifier's standard behaviour, if T is not final and neither are its equals
* and hashCode methods, is to require a reference to a subclass of T for which
* no instance can be equal to any instance of T, to make sure that subclasses that can redefine
* equals or hashCode don't break the contract; or it asks to call the usingGetClass
* method if T uses getClass() instead of instanceof in its equals method.
*
* Some may find that too strict for their liking; suppressing this warning disables that
* test.
*
* @see nl.jqno.equalsverifier.api.SingleTypeEqualsVerifierApi#withRedefinedSubclass(Class)
*/
STRICT_INHERITANCEUsage Examples:
// Non-final class without strict inheritance checks
public class Person { // Not final
private String name;
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Person)) return false;
Person other = (Person) obj;
return Objects.equals(this.name, other.name);
}
}
EqualsVerifier.forClass(Person.class)
.suppress(Warning.STRICT_INHERITANCE)
.verify();/**
* Disables the check that verifies equals is actually overridden.
*
* Can be used when a whole package of classes is automatically scanned and presented to
* EqualsVerifier, and one or more of them don't need to override equals.
*/
INHERITED_DIRECTLY_FROM_OBJECTUsage Examples:
// Class that doesn't override equals but is included in package scan
public class SimpleDataClass {
private final String value;
public SimpleDataClass(String value) {
this.value = value;
}
// No equals override - inherits from Object
}
// When scanning packages that include classes without equals
EqualsVerifier.forPackage("com.example.model")
.suppress(Warning.INHERITED_DIRECTLY_FROM_OBJECT)
.verify();/**
* Disables checks for NullPointerException within equals, hashCode and toString methods.
*
* Sometimes the constructor of a class makes sure no field can be null. If this is the case,
* and if the fields cannot be made null later in the lifecycle of the class by setters or other
* methods, suppress this warning to disable the check for NullPointerException.
*/
NULL_FIELDSUsage Examples:
// Class with constructor that prevents null fields
public class Person {
private final String name;
private final int age;
public Person(String name, int age) {
this.name = Objects.requireNonNull(name);
this.age = age;
}
@Override
public boolean equals(Object obj) {
// Safe to call methods on name without null check
// because constructor ensures it's never null
}
}
EqualsVerifier.forClass(Person.class)
.suppress(Warning.NULL_FIELDS)
.verify();/**
* Disables the check that all fields used in equals must also be used in hashCode.
*
* This is useful when bringing legacy systems under test, where you don't want to change the
* existing hashCode behaviour but you do want to use EqualsVerifier.
*
* Note that hashCodes with higher distributions give better performance when used in
* collections such as HashMap. Therefore, if possible, you should use all
* fields that are used in equals, in hashCode as well.
*/
STRICT_HASHCODEUsage Examples:
// Legacy class with inconsistent equals/hashCode
public class LegacyPerson {
private String firstName;
private String lastName;
private int socialSecurityNumber;
@Override
public boolean equals(Object obj) {
// Uses all three fields
}
@Override
public int hashCode() {
// Legacy implementation only uses firstName and lastName
return Objects.hash(firstName, lastName);
}
}
EqualsVerifier.forClass(LegacyPerson.class)
.suppress(Warning.STRICT_HASHCODE)
.verify();/**
* Disables the example check for cached hashCode.
*
* The example check verifies that the cached hashCode is properly initialized. You
* can use this, if creating an example object is too cumbersome. In this case, null can be
* passed as an example.
*
* Note that suppressing this warning can be dangerous and should only be done in unusual
* circumstances.
*/
NO_EXAMPLE_FOR_CACHED_HASHCODEUsage Examples:
// When providing example for cached hashCode is difficult
EqualsVerifier.forClass(ComplexCachedClass.class)
.withCachedHashCode("cachedHashCode", "calculateHashCode", null)
.suppress(Warning.NO_EXAMPLE_FOR_CACHED_HASHCODE)
.verify();/**
* Disables the check that fields marked with the @Id or @EmbeddedId annotations in JPA entities
* may not be used in the equals contract.
*
* When this warning is suppressed, the fields marked with @Id or @EmbeddedId will become the
* entity's surrogate key. Only these fields can now be part of the equals contract; all
* other fields may no longer be used in equals.
*/
SURROGATE_KEYUsage Examples:
// JPA Entity using ID as surrogate key
@Entity
public class UserEntity {
@Id
@GeneratedValue
private Long id;
private String username;
private String email;
@Override
public boolean equals(Object obj) {
if (!(obj instanceof UserEntity)) return false;
UserEntity other = (UserEntity) obj;
return Objects.equals(this.id, other.id); // Only uses ID
}
}
EqualsVerifier.forClass(UserEntity.class)
.suppress(Warning.SURROGATE_KEY)
.verify();/**
* Disables the check that fields marked with the @Id or @EmbeddedId annotations in JPA entities
* may not be used in the equals contract.
*
* When this warning is suppressed, all fields will become part of the entity's key, and
* EqualsVerifier will operate as if the entity were a normal class.
*/
SURROGATE_OR_BUSINESS_KEYUsage Examples:
// JPA Entity using business key (all fields)
@Entity
public class ProductEntity {
@Id
@GeneratedValue
private Long id;
private String name;
private String sku;
@Override
public boolean equals(Object obj) {
// Uses all fields including ID
}
}
EqualsVerifier.forClass(ProductEntity.class)
.suppress(Warning.SURROGATE_OR_BUSINESS_KEY)
.verify();/**
* Disables the check that collection fields in JPA, or @Basic fields marked with
* FetchType.LAZY, should be accessed through their getter methods in equals and
* hashCode methods.
*
* Normally, it is necessary to go through the getter for these fields, because their
* content may not be materialized in some instances. Calling the getter will materialize them,
* but referencing the field directly will not. This can lead to situations where the
* equals method of objects that should be equal to each other returns false, because
* one instance has the content materialized and the other does not.
*/
JPA_GETTERUsage Examples:
// JPA Entity that accesses fields directly instead of using getters
@Entity
public class OrderEntity {
@Id
private Long id;
@OneToMany(fetch = FetchType.LAZY)
private List<OrderItem> items;
@Override
public boolean equals(Object obj) {
// Accesses items field directly instead of getItems()
}
}
EqualsVerifier.forClass(OrderEntity.class)
.suppress(Warning.JPA_GETTER)
.verify();/**
* Disables the check that transient fields not be part of the equals contract.
*
* EqualsVerifier's standard behaviour is to disallow transient fields being used in
* equals and hashCode methods, since these fields may not be restored to their
* original state after deserialization, which would break equals.
*
* If measures are taken that this will never happen, this warning can be suppressed to
* disable EqualsVerifier's transience test.
*/
TRANSIENT_FIELDSUsage Examples:
// Class that uses transient fields in equals (with proper serialization handling)
public class CustomSerializableClass implements Serializable {
private String name;
private transient String cachedValue; // Used in equals but handled in serialization
@Override
public boolean equals(Object obj) {
// Uses transient field with proper handling
}
private void readObject(ObjectInputStream in) {
// Properly restore transient field during deserialization
}
}
EqualsVerifier.forClass(CustomSerializableClass.class)
.suppress(Warning.TRANSIENT_FIELDS)
.verify();/**
* Disables the check that equality of BigDecimal fields is implemented using
* compareTo rather than equals.
*
* BigDecimal objects that are equal using compareTo are not necessarily
* equal using equals, for example the values of 1 and 1.0.
* For variants of the same value to be considered equal, classes with BigDecimal
* fields should use compareTo to check equality of non-null BigDecimal
* references and produce the same hashcode for all equal variants.
*
* EqualsVerifier checks for this by default but it can be disabled by suppressing
* this warning.
*/
BIGDECIMAL_EQUALITYUsage Examples:
// Class that uses BigDecimal.equals() instead of compareTo()
public class Price {
private BigDecimal amount;
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Price)) return false;
Price other = (Price) obj;
return Objects.equals(this.amount, other.amount); // Uses equals, not compareTo
}
}
EqualsVerifier.forClass(Price.class)
.suppress(Warning.BIGDECIMAL_EQUALITY)
.verify();/**
* No longer does anything.
* @deprecated Use SingleTypeEqualsVerifierApi.withPrefabValuesForField(String, Object, Object) instead.
*/
@Deprecated
ZERO_FIELDSStandard IDE-generated code:
EqualsVerifier.forClass(MyClass.class)
.suppress(Warning.STRICT_INHERITANCE, Warning.NONFINAL_FIELDS)
.verify();JPA entities:
EqualsVerifier.forClass(MyEntity.class)
.suppress(Warning.SURROGATE_KEY, Warning.JPA_GETTER)
.verify();Legacy code migration:
EqualsVerifier.forClass(LegacyClass.class)
.suppress(Warning.STRICT_HASHCODE, Warning.NONFINAL_FIELDS, Warning.NULL_FIELDS)
.verify();Package scanning:
EqualsVerifier.forPackage("com.example.model")
.suppress(Warning.INHERITED_DIRECTLY_FROM_OBJECT, Warning.STRICT_INHERITANCE)
.verify();Install with Tessl CLI
npx tessl i tessl/maven-nl-jqno-equalsverifier--equalsverifier