Objects.equals()

Compare two values that may be null without writing null checks or risking a NullPointerException.

Signature
public static boolean equals(Object a, Object b)
  • Added in Java Java 7

Parameters

ParameterTypeDescription
$aObjectThe first value. May be null.
$bObjectThe second value. May be null.

Return value

booleantrue if both arguments are null or a.equals(b) is true; false if exactly one is null or a.equals(b) is false.

Errors

Nothing itself. Any exception thrown by a.equals(b) propagates.

Objects.equals(a, b) is a.equals(b) with the null cases handled for you. Two nulls are equal, one null and one value are not, and otherwise it calls a.equals(b). It replaces the clumsy a == null ? b == null : a.equals(b) that used to appear in every hand-written equals method.

Why it matters

Calling value.equals(other) throws NullPointerException when value is null. In back-end code that is common: an optional JSON field, a nullable database column, a missing query parameter. The old workaround of writing "ACTIVE".equals(status) works for a constant, but not when both sides are variables. Objects.equals is safe either way round.

if (Objects.equals(existing.email(), request.email())) {
    return; // nothing changed, skip the UPDATE
}

What it does not do

  • It does not change what equality means. It still delegates to the class's equals, so for a class that never overrode it you get identity comparison.
  • Arrays compare by reference, because arrays do not override equals. Use Arrays.equals or Objects.deepEquals for contents.
  • It does not convert types. Objects.equals(1L, 1) is false: a Long is never equal to an Integer. This bites when an id arrives as Integer from one source and Long from another.
  • It is not case-insensitive. For strings where case should not matter, use equalsIgnoreCase after a null check.

Inside equals methods

When you write equals for a class with nullable fields, compare each field with Objects.equals and pair it with Objects.hash over the same fields in hashCode. Records generate both for you, so reach for this mainly in ordinary classes that cannot be records. For JPA entities, compare a stable business key rather than every mutable field, or the entity changes its hash code while it sits in a set.

Back-end angle

Change detection is the typical use: comparing a stored entity with an incoming PATCH body to decide whether to write, publish an event or bump a version. Every field there can be null on either side, and Objects.equals keeps the comparison to one readable line per field.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Comparing a stored entity with an incoming update to decide whether a database write or change event is needed.
  • Implementing equals on the business key of a class that cannot be a record, where some of those fields are allowed to be null.
  • Checking an optional request field against a stored value without first testing either side for null.

Frequently asked questions

What is the difference between a.equals(b) and Objects.equals(a, b)?

They give the same answer when a is not null. If a is null, a.equals(b) throws NullPointerException, while Objects.equals(a, b) returns true if b is also null and false otherwise.

Why does Objects.equals return false for two arrays with the same contents?

Arrays do not override equals, so it falls back to identity. Use Arrays.equals for one-dimensional arrays or Objects.deepEquals for nested arrays.

Why is Objects.equals(1L, 1) false?

The arguments are boxed to Long and Integer, and Long.equals only returns true for another Long. Convert both values to the same type before comparing.

More examples

Writing equals for a class with nullable fields

The null phone number never causes a NullPointerException because each field goes through Objects.equals. hashCode uses the same fields, so equal objects get equal hash codes.

Example · java
final class Customer {
    private final long id;
    private final String email;
    private final String phone; // optional

    Customer(long id, String email, String phone) {
        this.id = id;
        this.email = email;
        this.phone = phone;
    }

    @Override
    public boolean equals(Object o) {
        return o instanceof Customer c
                && id == c.id
                && Objects.equals(email, c.email)
                && Objects.equals(phone, c.phone);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, email, phone);
    }
}

void main() {
    var a = new Customer(1, "[email protected]", null);
    var b = new Customer(1, "[email protected]", null);
    var c = new Customer(1, "[email protected]", "0113 000 0000");
    IO.println(a.equals(b));
    IO.println(a.equals(c));
    IO.println(a.hashCode() == b.hashCode());
}
// Output:
// true
// false
// true

Detecting which fields a PATCH request changed

Fields can be null on either side, and the comparison still reads as one line each. An empty list means the service can skip the database write entirely.

Example · java
record Profile(String name, String bio, String website) {}

List<String> changedFields(Profile before, Profile after) {
    var changed = new ArrayList<String>();
    if (!Objects.equals(before.name(), after.name())) changed.add("name");
    if (!Objects.equals(before.bio(), after.bio())) changed.add("bio");
    if (!Objects.equals(before.website(), after.website())) changed.add("website");
    return changed;
}

void main() {
    var stored = new Profile("Ada", null, "https://ada.example");
    var incoming = new Profile("Ada", "Engineer", null);
    IO.println(changedFields(stored, incoming));
    IO.println(changedFields(stored, stored));
}
// Output:
// [bio, website]
// []

Arrays compare by reference

Arrays inherit equals from Object, so Objects.equals only returns true for the very same array. Arrays.equals and Objects.deepEquals compare the contents.

Example · java
void main() {
    byte[] stored = {1, 2, 3};
    byte[] received = {1, 2, 3};

    IO.println(Objects.equals(stored, received));
    IO.println(Arrays.equals(stored, received));
    IO.println(Objects.deepEquals(stored, received));
    IO.println(Objects.equals(stored, stored));
}
// Output:
// false
// true
// true
// true

Discussion

  • Be the first to comment on this lesson.