Java Record hashCode: Default and Custom
java record hashcode: Learn how Java records implement hashCode by default, when to override it, and how to write a custom hashCode for record classes.
When you declare a Java record, the compiler generates implementations for equals, hashCode, and toString based on the record's components. For most value objects, the default java record hashcode is exactly what you need, but there are cases where you'll want to override it. Here's how the default works and when to replace it.
How the Default hashCode Is Computed
The compiler generates a hashCode method that combines the hash codes of all record components. The algorithm is similar to Arrays.hashCode or Objects.hash. For a record with components a, b, and c, the generated code looks like:
@Override public int hashCode() { int result = 1; result = 31 * result + (a == null ? 0 : a.hashCode()); result = 31 * result + (b == null ? 0 : b.hashCode()); result = 31 * result + (c == null ? 0 : c.hashCode()); return result; }
The exact implementation may vary by JDK version, but the contract is stable: equal records produce equal hash codes. The multiplier 31 is used because it's an odd prime that reduces collisions and is efficient to compute with shift and subtract.
Why hashCode Must Match equals
Records implement equals by comparing each component using Objects.equals. If two records have the same component values, they are equal. The default hashCode is designed to be consistent with that equality: if two records are equal, they will have the same hashCode. This is a requirement of the general contract for hashCode in Java. When you override equals, you must also override hashCode, and the same rule applies when you customize a record's equality behavior.
When to Override hashCode in a Record
The default hashCode is appropriate for most records that represent immutable data. However, there are situations where you might want a different hashCode:
- You want to use a subset of components for equality, but the default uses all components.
- You have a component that is expensive to hash, such as a large collection, and you want a cheaper hash based on a derived value.
- You need a hash that is stable across serialization or across JVM restarts, which the default does not guarantee.
- You want to avoid potential denial-of-service attacks by using a randomized hash seed, though this is rarely a concern for records.
If you override equals, you must override hashCode to maintain the contract. Even if you only override hashCode, you should ensure it remains consistent with equals.
How to Override hashCode in a Record
Overriding hashCode in a record is straightforward. You can provide your own implementation directly in the record body. For example, suppose you have a record Person with name and age, but you want equality based only on name:
public record Person(String name, int age) { @Override public boolean equals(Object obj) { if (this == obj) return true; if (!(obj instanceof Person other)) return false; return name.equals(other.name); } @Override public int hashCode() { return name.hashCode(); } }
Notice that the record still has both components, but equality and hash are based on name alone. This changes the semantics of the record; it no longer behaves as a pure value object for all fields. Use this pattern only when you intentionally want a different identity concept.
Performance and Caching Considerations
The default hashCode is recomputed on every call. For records with many components or expensive hashCode methods on components, this can be a performance concern if the record is used heavily in hash-based collections. Because records are immutable, you can cache the hashCode by overriding it and storing the value in a field. For example:
public record Point(int x, int y) { private int cachedHash; @Override public int hashCode() { int h = cachedHash; if (h == 0) { h = 31 * x + y; cachedHash = h; } return h; } }
This pattern is safe because x and y are final and cannot change. However, the default hashCode is already fast for simple primitive components, so caching may only be beneficial when component hashing is expensive, such as when a component is a large List or Map.
Common Pitfalls with Record hashCode
One common mistake is overriding equals without overriding hashCode, which breaks the contract and causes incorrect behavior in HashMap and HashSet. Another pitfall is using mutable components in a record. Records are supposed to be immutable, but if a component is a mutable object like an ArrayList, the record's hashCode changes when the list changes, making it unsafe for hash-based collections. Always ensure that record components are effectively immutable, or copy them defensively.
A subtle issue is relying on the default hashCode for records that are used as keys in distributed systems or persisted data. The default hashCode is not guaranteed to be stable across JDK versions, so if you need a stable hash, you should override it with a deterministic algorithm.
Custom hashCode for a Record with a Collection Component
Consider a record that holds a list of integers:
public record NumberList(List<Integer> numbers) { @Override public int hashCode() { return numbers.hashCode(); } }
This works, but if the list is mutable, the hashCode changes when the list is modified. A safer approach is to make a defensive copy in the constructor, but that adds overhead. Alternatively, you can compute a hash based on the contents without relying on the list's own hashCode, for example by iterating and combining each element's hash. The choice depends on whether you need stability and how you use the record.