The equals() Method in Java: Comparing Objects and Overriding equals()
Two objects holding identical data can still turn out to be "not equal": new String("Java") == new String("Java") returns false, even though both strings contain the same word. That's because == compares references, not content. To compare objects in Java, you need the equals() method.
The equals() method is a method of the java.lang.Object class. It checks whether a given object is equal to the current one and returns a boolean: true if the objects are considered equal, false otherwise. The default implementation compares references only. That's why classes where logical equality matters (String, Integer, LocalDate, your own entity classes) override equals() and compare field values instead.
How Object.equals() works by default
Every class in Java extends Object, directly or indirectly, so every object has an equals() method. Its original implementation in the JDK looks like this:
public boolean equals(Object obj) {
return (this == obj);
} In other words, until a class overrides equals(), the method behaves exactly like the == operator. It returns true only if both references point to the same object in memory. If the difference between a variable and the object it refers to isn't clear yet, see passing objects to methods in Java.
Important
You'll often read that "equals() compares object content." That's true only for classes that override it: String, wrapper classes (Integer, Double…), collections, LocalDate, and so on. If your own class doesn't override equals(), it compares references, exactly like ==.
Standard library classes already override equals(), so content comparison "just works" for them:
String s1 = new String("Java");
String s2 = new String("Java");
System.out.println(s1 == s2); // false - different objects
System.out.println(s1.equals(s2)); // true - same characters
List<Integer> list1 = List.of(1, 2, 3);
List<Integer> list2 = new ArrayList<>(List.of(1, 2, 3));
System.out.println(list1.equals(list2)); // true - same elements in the same order == vs equals() in Java
For reference types, == checks identity (is it the same object?), while equals() checks logical equality (do the objects mean the same thing?). For primitives (int, double, boolean…) only == is used: primitives have no methods, and the values themselves are compared. So if you need to check whether two objects are equal in Java, the right tool depends on what "equal" means for you:
| Operator/method | What it compares | Behavior with null | When to use it |
|---|---|---|---|
a == b | Values for primitives, references for objects | Safe: null == null is true | Primitives, enum, checking "is this the exact same object?" |
a.equals(b) | Logical equality (as defined in a's class) | Throws NullPointerException if a == null; a.equals(null) returns false | Comparing objects when a is guaranteed not to be null |
Objects.equals(a, b) | Same as a.equals(b), plus correct null handling | Safe: both null gives true, one null gives false | Comparing objects when either one may be null |
Use equals() or Objects.equals() to compare objects by content (strings, dates, wrapper numbers, entities). Keep == for primitives, enum constants, and cases where you really do need to check reference identity.
How to override equals(): an example
For your own class, you decide what logical equality means, that is, which fields make two objects "the same." Take a Person class with three fields: fullName, age, and retired. The overridden equals() method uses all three. If a field shouldn't affect equality (say, an internal counter or cache), you can leave it out, but then you must leave it out of hashCode() too.
import java.util.Objects;
public class Person {
private final String fullName;
private final int age;
private final boolean retired;
public Person(String fullName, int age, boolean retired) {
this.fullName = fullName;
this.age = age;
this.retired = retired;
}
public String getFullName() {
return fullName;
}
public int getAge() {
return age;
}
public boolean isRetired() {
return retired;
}
@Override
public boolean equals(Object o) {
if (this == o) { // 1. same reference
return true;
}
if (o == null || getClass() != o.getClass()) { // 2. null or different class
return false;
}
Person person = (Person) o; // 3. cast
return age == person.age // 4. compare fields
&& retired == person.retired
&& Objects.equals(fullName, person.fullName);
}
@Override
public int hashCode() {
return Objects.hash(fullName, age, retired);
}
} Here's what each step of equals() does:
- Reference check
this == o. If it's the same object, there's nothing left to check, so the method returnstrueright away. This is a quick optimization. - Null and class check. By contract,
x.equals(null)must always returnfalse. ComparinggetClass()guarantees that an object of a different class (including a subclass) will never equal aPerson. - Cast to
Person. After the class check, the cast is safe. - Compare the significant fields. Primitives (
int,boolean) are compared with==. Reference fields are compared withObjects.equals(), which correctly handles the case wherefullNameisnull. It's shorthand forfullName != null ? fullName.equals(person.fullName) : person.fullName == null.
You don't have to write this code by hand. IntelliJ IDEA generates equals() and hashCode() via Code → Generate (Alt+Insert), and Eclipse does it via Source → Generate hashCode() and equals().
Now let's create two objects, person1 and person2, with identical field values, and a third variable, person3, that points to person1:
public class PersonExample2 {
public static void main(String[] args) {
Person person1 = new Person("John Smith", 56, false);
Person person2 = new Person("John Smith", 56, false);
Person person3 = person1;
System.out.println("person1 == person2? " + (person1 == person2));
System.out.println("person1 == person3? " + (person1 == person3));
System.out.println("person1.equals(person2)? " + person1.equals(person2));
System.out.println("person1.equals(person3)? " + person1.equals(person3));
}
} Output:
person1 == person2? false
person1 == person3? true
person1.equals(person2)? true
person1.equals(person3)? true What's happening here:
person1 == person2isfalse: these are two different objects, even though their data is identical.person1 == person3istrue: both variables reference the same object.person1.equals(person2)istrue: the overridden method compares fields, and they match.person1.equals(person3)istrue: the very first check,this == o, succeeds.
If you remove the overridden equals() from Person, person1.equals(person2) prints false instead, because the implementation inherited from Object compares references.
The equals() contract
The Object class documentation requires every equals() implementation to satisfy five properties for non-null references x, y, z. Collections (HashMap, HashSet, ArrayList.contains(), and others) rely on these properties. Breaking them causes bugs that are hard to track down.
| Property | Rule | What it means in practice |
|---|---|---|
| Reflexive | x.equals(x) is always true | An object is equal to itself |
| Symmetric | x.equals(y) == y.equals(x) | The order of comparison doesn't matter |
| Transitive | If x.equals(y) and y.equals(z), then x.equals(z) | Equality "propagates" through a chain |
| Consistent | Repeated calls to x.equals(y) return the same result | As long as the compared fields don't change |
| Comparison with null | For any non-null x, x.equals(null) is false | No NullPointerException inside equals() |
equals() and hashCode()
The equals() contract is tightly linked to hashCode(): if two objects are equal according to equals(), their hashCode() values must also be equal. The reverse isn't required: different objects can have the same hash code (a collision).
Hash-based collections (HashMap, HashSet) first find a "bucket" using hashCode() and only then call equals(). Suppose you override equals() but keep the hashCode() inherited from Object. Two logically equal objects will then almost certainly land in different buckets, and the collection won't recognize them as equal. That's why both methods are overridden together, using the same set of fields. The Person example above does this with Objects.hash(...).
Tip
Keep mutable fields out of equals() and hashCode() for objects you store in a HashSet or use as HashMap keys. If such a field changes after the object is added, its hash code changes too. The object is still physically in the collection, but lookups can no longer find it.
Objects.equals(): null-safe comparison
Calling a.equals(b) throws a NullPointerException when a is null. To save you from writing null checks by hand, Java 7 added the utility method java.util.Objects.equals(Object a, Object b). It works like this:
- if
aandbare the same reference (including both beingnull), it returnstrue; - if exactly one of the arguments is
null, it returnsfalse; - otherwise, it returns the result of
a.equals(b).
String name = null;
// System.out.println(name.equals("John")); // NullPointerException!
System.out.println("John".equals(name)); // false - literal on the left, no NPE
System.out.println(Objects.equals(name, "John")); // false
System.out.println(Objects.equals(null, null)); // true Putting the constant on the left ("John".equals(name)) also protects against NullPointerException, but it only works when one side is guaranteed not to be null. Objects.equals() works in every case, so it's the recommended way to compare fields inside your own equals(). For arrays, use Objects.deepEquals(), which compares content, including nested arrays.
getClass() vs instanceof, pattern matching and records
There are two common ways to check the type inside equals(). The choice matters as soon as inheritance is involved:
getClass() != o.getClass(): the object is equal only to an instance of exactly the same class. A subclass will never equal its parent, but symmetry is guaranteed. IntelliJ IDEA's default template generates this variant.o instanceof Person: subclasses pass the check too. This is natural forfinalclasses and interface hierarchies. It becomes risky if a subclass adds its own fields and overridesequals():person.equals(employee)might then returntruewhileemployee.equals(person)returnsfalse, which breaks symmetry.
Since Java 16, pattern matching lets you combine the instanceof check and the cast in one step, which makes the code shorter:
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Person other)) return false; // check + cast; false for null
return age == other.age
&& retired == other.retired
&& Objects.equals(fullName, other.fullName);
} You don't need a separate null check here, because null instanceof Person is always false.
If a class simply carries data, it's often easier to declare it as a record (Java 16+). The compiler generates equals(), hashCode(), and toString() automatically from all of its components:
public record PersonRecord(String fullName, int age, boolean retired) { }
PersonRecord r1 = new PersonRecord("John Smith", 56, false);
PersonRecord r2 = new PersonRecord("John Smith", 56, false);
System.out.println(r1.equals(r2)); // true
System.out.println(r1 == r2); // false Common equals() mistakes and gotchas
- Overloading instead of overriding. A method like
public boolean equals(Person p)doesn't overrideequals(Object). It creates a new, overloaded method. Collections callequals(Object), solist.contains(person)ends up comparing references. The@Overrideannotation turns this mistake into a compile error, so always add it. - Calling
equals()onnull.str.equals("abc")throws aNullPointerExceptionifstr == null. Use"abc".equals(str)orObjects.equals(str, "abc")instead. - Comparing strings with
==. Sometimes this "works" thanks to the string pool (literals are interned). But strings fromnew String(), runtime concatenation,Scanner, or a database are separate objects. - Expecting
equals()to compare arrays. Arrays don't overrideequals(), soarr1.equals(arr2)compares references. UseArrays.equals(arr1, arr2)to compare content, andArrays.deepEquals()for multidimensional arrays. StringBuilderandStringBuffer. They don't overrideequals()either. Compare them withsb1.toString().equals(sb2.toString()). Since Java 11, both classes also implementComparable, so you can usesb1.compareTo(sb2) == 0.BigDecimaltakes scale into account.new BigDecimal("1.0").equals(new BigDecimal("1.00"))returnsfalsebecause the two numbers have a different number of decimal places. To compare the values, usecompareTo(...) == 0.- Comparing
doubleandfloatfields with==. Insideequals(), useDouble.compare(a, b) == 0(orFloat.compare) instead. It handlesNaNand the difference between0.0and-0.0correctly, and it stays consistent withDouble.hashCode(). - Overriding
equals()but forgettinghashCode(). Objects "get lost" in aHashSetorHashMap. See the equals() and hashCode() section above.
Frequently asked questions
Can I compare strings in Java with ==?
Not if you need to compare their text. The == operator checks whether two variables point to the same object. Two string literals like "Java" usually share the same reference thanks to the string pool. A string created with new String("Java"), read from user input, or built by runtime concatenation is a different object, though, and == returns false. Always use equals() to compare strings, or equalsIgnoreCase() if case doesn't matter.
Why does Integer 127 == 127 return true, but 128 == 128 returns false?
During autoboxing, Integer a = 127 calls Integer.valueOf(), which by default caches objects for values from -128 to 127. For 127, both variables reference the same cached object. For 128, two distinct objects are created, so == returns false. Always compare wrapper objects (Integer, Long, etc.) with equals(), or unbox them to primitives first.
Do I need to override equals() for enum and record types?
No. In an enum, equals() is declared final and compares references, and each constant exists as a single instance. So enum constants can safely be compared with ==, which also avoids NullPointerException. For a record, the compiler generates equals() and hashCode() automatically from all of its components. Override them only if you need special logic, for example to compare an array component by content.
How do I compare two arrays in Java?
Arrays inherit equals() from Object, so it compares references: new int[]{1, 2}.equals(new int[]{1, 2}) returns false. To compare content, use Arrays.equals(a, b). For multidimensional arrays, use Arrays.deepEquals(a, b).
What happens if I override equals() but not hashCode()?
The code compiles, but hash-based collections start behaving incorrectly. For example, you can add two objects that are equal according to equals() to a HashSet, and both get stored. Meanwhile, set.contains(new Person(...)) returns false for data that's already there. The reason is that equal objects get different hash codes from Object.hashCode(), so they land in different buckets. The rule is simple: if you override one method, override the other using the same fields.
What's the difference between equals() and compareTo()?
equals() is defined in Object and only answers "equal or not" with a boolean. compareTo() comes from the Comparable interface and returns an int that tells you the order: negative, zero, or positive. Sorted collections such as TreeSet and TreeMap use compareTo() rather than equals() to detect duplicates. The two methods should be consistent, meaning compareTo() returns 0 exactly when equals() returns true. BigDecimal is a well-known exception: 1.0 and 1.00 are equal by compareTo() but not by equals().
Comments