Java hashCode Override: Contract and Pitfalls
java hashcode override: Learn how to correctly override hashCode() in Java, satisfy the equals/hashCode contract, and avoid common bugs in HashMap and HashSet usage.
When you override equals() in Java, the hashCode() method must be overridden as well, or hash-based collections like HashMap and HashSet will misbehave. The java hashcode override rule exists because of the hashCode contract: equal objects must have equal hash codes. If two objects are equal according to equals() but return different hash codes, they can be stored in different buckets, and lookups will fail even though the object is present in the collection.
The hashCode Contract
The Object class defines three rules for hashCode():
- During one execution of an application, calling
hashCode()on the same object must return the same integer, provided no information used inequals()changes. - If two objects are equal according to
equals(), their hash codes must be equal. - If two objects are unequal, their hash codes may be equal; unequal hash codes are not required, but equal hash codes improve performance by reducing collisions.
The second rule is the one most often violated. It is a one-way implication: equal objects must share a hash code, but unequal objects may also share one. Violating the first rule happens when a field used in equals() changes after the object is placed in a hash-based collection.
Why Hash Collections Depend on hashCode
HashMap and HashSet store entries in an array of buckets. When you insert a key, the map computes key.hashCode() and uses it to pick a bucket. When you look up a key, it computes the hash again and searches only that bucket. If the stored key and the lookup key are equal but produce different hash codes, the lookup searches the wrong bucket and returns nothing.
This is not an edge case. Any class that overrides equals() without overriding hashCode() is broken for hash-based collections. The compiler will not warn you, and the failure appears only at runtime, often as a missing element or a null value from a map lookup.
A Minimal Correct Implementation
Here is a straightforward implementation for a class with two fields:
public final class User { private final String email; private final int roleId; public User(String email, int roleId) { this.email = email; this.roleId = roleId; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof User)) { return false; } User other = (User) obj; return roleId == other.roleId && Objects.equals(email, other.email); } @Override public int hashCode() { int result = 1; result = 31 * result + (email == null ? 0 : email.hashCode()); result = 31 * result + roleId; return result; } }
The 31 * result + fieldHash pattern is the classic approach. The multiplier 31 is a prime, which spreads hash values more evenly across buckets than a smaller multiplier would. The starting value of 1 is arbitrary but consistent; what matters is that the same fields are used in the same order for every instance.
The Objects.equals call in equals() handles null safely. The hash code does the same with the explicit null check.
Using Objects.hash for Simpler Code
Java 7 introduced Objects.hash(Object...), which produces the same style of combined hash:
@Override public int hashCode() { return Objects.hash(email, roleId); }
This is more readable, but it boxes primitive arguments. For a class with a few fields, the cost is negligible. For a class whose hash code is computed frequently, such as a key in a hot code path, the manual 31 * result form avoids the allocation and boxing overhead.
The two forms must agree with each other only in the sense that equal objects produce equal hash codes. You can mix them across classes as long as each class is internally consistent.
Choosing Fields for the Hash
Use exactly the fields that equals() uses, in the same order. If equals() ignores a field, the hash must ignore it too; otherwise equal objects can produce different hashes. Conversely, including a field in the hash that equals() ignores is allowed but wasteful, and it can cause surprising behavior if that field changes.
For a boolean field, use (field ? 1 : 0). For a byte, short, char, or int, use the value directly. For a long, combine the high and low halves: (int) (value ^ (value >>> 32)). For a float, use Float.floatToIntBits(value). For a double, use Double.doubleToLongBits(value) and then combine the long. For arrays, use Arrays.hashCode, or Arrays.deepHashCode for nested arrays.
Common Mistakes That Break Collections
The most common mistake is overriding equals() and forgetting hashCode() entirely. The second most common is using a mutable field in both methods and then mutating the object after it is stored in a HashMap or HashSet.
Map<User, String> scores = new HashMap<>(); User user = new User("alice@example.com", 1); scores.put(user, "active"); user.setRoleId(2); // hashCode() now returns a different value scores.get(user); // returns null
After setRoleId(2), the object's hash code changes, but the map already placed it in the bucket computed from the old hash. The lookup computes the new hash, searches the wrong bucket, and finds nothing. The entry is still in the map but effectively unreachable.
The fix is to make fields used in equals() and hashCode() final, or to remove the object from the collection before mutating it. For value objects, final fields are the cleaner choice.
Performance and Collision Behavior
The hash code does not need to be unique. Collisions are expected; HashMap handles them with linked lists, or with trees when a bucket grows large in newer JDK implementations. What matters is that the hash values are well distributed across the integer range. Using a small set of fields or a weak combination produces many collisions, which degrades lookups from near-constant time to linear scans within a bucket.
A common weak pattern is XOR-ing fields without scaling:
return email.hashCode() ^ roleId; // poor distribution
XOR can produce the same result for different field combinations, especially when values are small. The 31 * result pattern shifts the accumulated value and spreads the contribution of each field across the bits.
If the class is used as a key in a performance-sensitive map, consider caching the hash code in a final field. This trades a small memory cost for avoiding recomputation on every lookup. It only works when the fields are immutable.
Mutable Fields and Hash Stability
The first rule of the contract says the hash code must remain stable as long as the fields used by equals() do not change. If those fields can change, the object's hash code can change while it is stored in a hash-based collection. That breaks the collection's internal structure.
For entities with mutable identifiers, such as a database row whose fields change over time, using the object directly as a map key is risky. A common alternative is to use an immutable key, such as the primary key value, and store the mutable entity as the map value.
When the Default Identity Hash Is Correct
If you never override equals(), you should not override hashCode() either. The default implementations use identity: each object is equal only to itself, and the hash code is derived from the object's memory address or an identity-based counter. This is correct for classes where object identity matters, such as locks, threads, or mutable service objects.
The decision to override hashCode() should always follow the decision to override equals(). If equality is based on field values, both methods must be based on the same fields. If equality is identity-based, leave both methods alone.