Interviewers love this because one tiny symbol hides a huge Java idea: identity vs value.
Question: Difference between == and equals().
Answer: == checks whether two references point to the same object, and for primitives it compares the actual values. equals() is a method for logical equality; by default in Object it behaves like ==, but many classes such as String override it to compare content. The big idea is: use == for identity and primitives, and use equals() for value comparison.
Interview-Ready Answer: In Java, == compares primitive values directly, but for objects it compares whether both references point to the exact same object. equals() is meant for logical equality; for example, String.equals() compares text content even when the two string objects are different. My rule is: use == for primitives and identity checks, and use equals() for meaningful value comparison, ideally with hashCode() overridden too when I create my own class.
Think of objects as boxes. == asks whether it is the same box; equals() asks whether the contents should be considered the same.
| Aspect | == | equals() |
|---|---|---|
| What it checks | Identity | Logical value |
| Primitives | Yes | No |
| Default on objects | Same object | Same object |
| Custom behavior | No | Yes, if overridden |
| Null handling | Safe to compare with null | Can throw NullPointerException if left side is null |
int a = 5; and int b = 5; make a == b true.== checks whether both references point to the same object in memory.equals() is a normal instance method. If a class does not override it, Object.equals() simply falls back to identity, which is effectively the same as ==.String, wrapper types, and many value classes override equals() to compare contents instead of reference identity.equals() only after a bucket is chosen by hashCode(), so the equals() and hashCode() contract must stay consistent.== for primitives and intentional identity checks.equals() for IDs, names, dates, money objects, and any value object.Objects.equals(a, b) when either side may be null.Arrays.equals or Arrays.deepEquals; array equals() is inherited from Object.== is constant time, O(1). equals() is also O(1) for many simple objects, but can be O(n) when it scans content, like strings, lists, or large byte arrays. If two strings have 1,000 characters, the comparison may need to check many of them, so it is still fast but not free. A subtle bug is writing equals(Person p) instead of equals(Object o): that creates an overload, not an override, so collections and framework code ignore it.
Real-World Example: In an e-commerce checkout service, order IDs came from a database query in one path and from an HTTP request in another. A developer compared them with == because the values looked identical in logs, but the references were different, so the service incorrectly treated valid orders as missing. Customers saw 404 or order-not-found errors right after payment, and logs showed the same characters with different identity hashes. The fix was to use equals() for the ID and to add tests that create the same text through different code paths. A similar bug can happen with equals() on custom objects that never override it: the code silently falls back to identity comparison and duplicates appear in sets and maps.
import java.util.Objects;
public class EqualityDemo {
public static void main(String[] args) {
// Primitives: == compares the actual value.
int a = 42;
int b = 42;
// Strings: == checks identity, equals() checks text content.
String s1 = "java";
String s2 = new String("java");
// Boxed integers: == can look correct for small values because of caching.
Integer i1 = 127;
Integer i2 = 127;
Integer i3 = 128;
Integer i4 = 128;
// Custom value object: equals() is overridden to compare fields.
Person p1 = new Person("Ava", 30);
Person p2 = new Person("Ava", 30);
Person p3 = new Person("Ava", 31);
Person maybeNull = null;
print("int a == b", a == b);
print("s1 == s2", s1 == s2);
print("s1.equals(s2)", s1.equals(s2));
print("i1 == i2 (127 is usually cached)", i1 == i2);
print("i3 == i4 (128 is usually not cached)", i3 == i4);
print("p1 == p2", p1 == p2);
print("p1.equals(p2)", p1.equals(p2));
print("p1.equals(p3)", p1.equals(p3));
// Edge case: calling equals() on a null reference throws NPE.
try {
System.out.println("maybeNull.equals(p1) -> " + maybeNull.equals(p1));
} catch (NullPointerException ex) {
System.out.println("maybeNull.equals(p1) -> NPE");
}
// Null-safe comparison helper.
System.out.println("Objects.equals(maybeNull, p1) -> " + Objects.equals(maybeNull, p1));
// When equals() is overridden correctly, equal objects must share the same hash code.
System.out.println("p1.hashCode() == p2.hashCode() -> " + (p1.hashCode() == p2.hashCode()));
}
private static void print(String label, boolean value) {
System.out.println(label + " -> " + value);
}
static final class Person {
private final String name;
private final int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Person)) return false;
Person person = (Person) o;
return age == person.age && Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
}Follow-up & Tricky Questions:
Object.equals() do by default? It behaves like ==, so it returns true only when both references point to the same object. That is why custom classes usually need their own override.equals() and hashCode() be overridden together? Hash-based collections such as HashMap and HashSet use hashCode() first and then equals() for collisions. If equal objects have different hash codes, they can end up in different buckets and look unequal to the collection.equals() compare primitive types? No, primitives do not have methods. You compare primitives with ==; if they are boxed into wrapper objects, then you can call equals().HashMap use equality? It computes the key's hash to find a bucket, then uses equals() to find the exact matching key inside that bucket. So a bad equals() implementation can make lookups fail even when the data looks correct.Objects.equals(a, b) for? It is a null-safe helper that returns true if both are null, false if only one is null, and otherwise delegates to a.equals(b). It is a good default when either side might be missing.Integer a = 127; Integer b = 127; a == b be true? Java usually caches wrapper objects for small values, so both variables may point to the same cached object. That is a trap: the result is about caching, not about numeric equality.new String("java").equals("java") return true? Yes, because equals() compares the characters, not the object identity. But == is false because they are two different objects.equals() by contents? No, arrays inherit Object.equals(), so == and equals() both check identity unless you use Arrays.equals or Arrays.deepEquals.== for Strings. Correction: use equals() for text content; == only tells you whether both references are the same object.equals() but forgetting hashCode(). Correction: always override both together so hash-based collections behave correctly.equals() on a possibly null reference. Correction: use Objects.equals(a, b) or check for null first.equals(). Correction: use Arrays.equals or Arrays.deepEquals for array content comparison.== asks, 'Is this the same house?' equals() asks, 'Do these two houses have the same layout and contents?'
== on primitives means value comparison.== on objects means reference identity.equals() is for logical equality.Object.equals() behaves like identity.equals(), also override hashCode().Objects.equals.new String and print == and equals().Money or Point, override equals() and hashCode(), and test it inside a HashSet.== is correct, one where equals() is correct, and one where Objects.equals() is safest.