skip to content

How do you compare two object references for value equality safely when either might be null, and why is x.equals(y) risky?

level: juniorimportance: should knowfreq 60%

answer

  1. x.equals(y) NPEs when x (receiver) is null
  2. Objects.equals(a,b): both null=true, one null=false, else a.equals(b)
  3. argument null is fine: "h".equals(null)=false
  4. constant-first trick: "OK".equals(status)
  5. == is null-safe but tests identity, not value

basics

~10 s

Calling x.equals(y) throws NullPointerException if x is null. Use Objects.equals(x, y) instead — it handles nulls (both null returns true, one null returns false) and otherwise calls x.equals(y).

solid answer

~50 s

equals() is an instance method, so x.equals(y) dereferences x — if x is null you get a NullPointerException before any comparison happens. The standard fix is java.util.Objects.equals(x, y), a null-safe static helper: it returns true if both are null, false if exactly one is null, and otherwise delegates to x.equals(y). This is the idiomatic way to compare possibly-null references and is also what you use field-by-field inside a hand-written equals() implementation. A common defensive trick when comparing against a constant is to put the non-null operand first: "OK".equals(status) never NPEs even if status is null. Note this is about logical equality; if you only need identity, == is inherently null-safe (null == x just evaluates without throwing). Don't confuse the two: == won't compare contents, and Objects.equals won't tell you if they're the same object.

code

java · 8 lines
java
String status = null;

// status.equals("ACTIVE");          // NPE: null receiver

boolean a = Objects.equals(status, "ACTIVE"); // false, no NPE
boolean b = "ACTIVE".equals(status);          // false, constant-first trick
boolean c = Objects.equals(null, null);       // true
boolean d = "x".equals(null);                 // false (null argument is fine)

go deeper

for a junior

Knows that x.equals(y) can NPE on a null x and that Objects.equals(x,y) is the safe alternative.

for a middle

Explains the both-null/one-null semantics, uses Objects.equals when writing equals(), and knows == is null-safe but identity-only.

for a senior

Distinguishes receiver-null vs argument-null, applies constant-first and Objects.equals consistently, and ties it into correct equals/hashCode authoring.

for a principal

Sets null-handling and equality conventions across a codebase (e.g. nullability annotations, Optional vs null, Objects utilities) and reasons about where null should be impossible by design.

## The hazard `equals()` is an **instance method** declared on `Object`. To call `x.equals(y)`, the JVM must first **dereference** `x` (find the object `x` points to). If `x` is `null`, there is no object — so you get a **`NullPointerException` (NPE)** *before* any comparison even runs. ```java String x = null; x.equals("hello"); // throws NullPointerException ``` Note that the *argument* being null is fine — `"hello".equals(null)` simply returns `false` (the `equals` contract requires returning false for a null argument). Only a null **receiver** (the thing before the dot) is the problem. ## The fix: `Objects.equals` `java.util.Objects.equals(a, b)` is a static helper that is **null-safe**. Its logic is: ```java public static boolean equals(Object a, Object b) { return (a == b) || (a != null && a.equals(b)); } ``` Reading that: - If `a` and `b` are the *same reference* (including **both null**) → `true`. - Else if `a` is null (and `b` isn't) → the `&&` short-circuits → `false`. - Else → delegate to `a.equals(b)` safely (we know `a` isn't null). So: ```java Objects.equals(null, null); // true Objects.equals(null, "x"); // false Objects.equals("x", null); // false Objects.equals("x", "x"); // true ``` This is the **idiomatic** way to compare two references that might be null, and it's exactly what you call **field by field** when hand-writing an `equals()` method (each object field may be null). ## The constant-first trick When comparing a possibly-null variable against a known non-null constant, put the **constant first** so the receiver is never null: ```java if ("ACTIVE".equals(status)) { ... } // safe even if status == null ``` Many teams treat this as a style rule (and some IDE inspections suggest it). `Objects.equals` works too and reads more symmetrically. ## Don't confuse with `==` `==` is **inherently null-safe** because it doesn't call a method — `null == x` just evaluates to a boolean without dereferencing. But `==` tests **identity**, not value, so it answers a *different* question. Use: - `Objects.equals(x, y)` → null-safe **value** comparison. - `x == y` → **identity** comparison (and the only correct tool for primitives, where there's no null and no `equals`). ## Bonus: `Objects.requireNonNull` is a different tool Don't confuse `Objects.equals` with `Objects.requireNonNull(x)`, which *throws* if `x` is null (a guard for fail-fast validation), or `Objects.hashCode(x)` / `Objects.hash(...)` used in the matching `hashCode()`.

  • What does "hello".equals(null) return, and why no exception?
    It returns false. The receiver "hello" is non-null, so no dereference fails; the equals contract simply mandates returning false for a null argument.
  • Inside a hand-written equals(), why use Objects.equals on each field?
    Because any object-typed field may itself be null. Objects.equals(this.f, other.f) compares them without risking an NPE when a field is null, keeping the implementation correct and concise.

saying these in an interview costs you the question

  • Thinking equals() never throws (null receiver does)
  • Believing a null argument throws (it returns false)
  • Using == to dodge NPE when you actually need value comparison
  • Confusing Objects.equals with Objects.requireNonNull
  • Assuming Objects.equals compares identity

context