Objects.equals()
Compare two values that may be null without writing null checks or risking a NullPointerException.
public static boolean equals(Object a, Object b)- Added in Java Java 7
Parameters
| Parameter | Type | Description |
|---|---|---|
$a | Object | The first value. May be null. |
$b | Object | The 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. UseArrays.equalsorObjects.deepEqualsfor contents. - It does not convert types.
Objects.equals(1L, 1)is false: aLongis never equal to anInteger. This bites when an id arrives asIntegerfrom one source andLongfrom another. - It is not case-insensitive. For strings where case should not matter, use
equalsIgnoreCaseafter 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
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.
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
// trueDetecting 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.
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.
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