Can you use Record as a key in HashMap
Record automatically gets correct equals() and hashCode(), which is critically important for HashMap to work.
🟢 Junior Level
Yes, Record is a great key for HashMap! This is one of the best roles for Record.
Record automatically gets correct equals() and hashCode(), which is critically important for HashMap to work.
public record CacheKey(String tenantId, String entityType, String entityId) {}
Map<CacheKey, Object> cache = new HashMap<>();
CacheKey key1 = new CacheKey("t1", "user", "u1");
cache.put(key1, userData);
// Another object with the same values — will find the data!
CacheKey key2 = new CacheKey("t1", "user", "u1");
cache.get(key2); // userData ✅
Why Record is good as a key:
- ✅ Immutable — key won’t change after being added
- ✅ equals/hashCode auto-generated
- ✅ Compact and clear
🟡 Middle Level
How it works
Key contract in HashMap:
- If
a.equals(b), thena.hashCode() == b.hashCode() - Key must not change after being added to Map
Record satisfies both conditions:
public record Point(int x, int y) {}
Point p1 = new Point(1, 2);
Point p2 = new Point(1, 2);
// Contract is fulfilled
assert p1.equals(p2); // true
assert p1.hashCode() == p2.hashCode(); // true
Common Mistakes
- Mutable components in key:
```java
public record BadKey(List
values) {}
BadKey key = new BadKey(new ArrayList<>(List.of(“a”, “b”))); map.put(key, “value”);
key.values().add(“c”); // Changed! hashCode is no longer correct map.get(key); // May not find!
**Solution:**
```java
public record GoodKey(List<String> values) {
public GoodKey {
values = List.copyOf(values); // immutable
}
}
- Array in key: ```java public record ArrayKey(int[] ids) {}
ArrayKey k1 = new ArrayKey(new int[]{1, 2, 3}); ArrayKey k2 = new ArrayKey(new int[]{1, 2, 3});
k1.equals(k2); // false! Arrays compare by reference k1.hashCode() == k2.hashCode(); // most likely false
**Solution:**
```java
public record ArrayKey(int[] ids) {
@Override
public boolean equals(Object o) {
if (!(o instanceof ArrayKey other)) return false;
return Arrays.equals(ids, other.ids);
}
@Override
public int hashCode() {
return Arrays.hashCode(ids);
}
}
Practical Application
1. Cache:
public record CacheKey(String tenantId, String query, List<String> params) {
public CacheKey {
params = List.copyOf(params);
}
}
Map<CacheKey, List<User>> queryCache = new ConcurrentHashMap<>();
2. Composite key:
public record OrderItemId(String orderId, String itemId) {}
Map<OrderItemId, OrderItem> items = new HashMap<>();
items.put(new OrderItemId("order-1", "item-1"), item);
🔴 Senior Level
Internal Implementation
How HashMap uses hashCode:
// HashMap.internal hash
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
// Record hashCode is generated by compiler through an optimized multiplication chain,
// NOT through Objects.hash(). Objects.hash creates an Object[] and is slower.
// Auto-generated hashCode for Record:
Auto-generated hashCode for Record:
public record User(String name, int age) {
// Auto-generated:
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
// Objects.hash uses Arrays.hashCode:
// int result = Arrays.hashCode(new Object[]{name, age});
Architectural Trade-offs
Record vs regular class as key:
| Aspect | Record | Regular class |
|---|---|---|
| equals/hashCode | Auto, correct | Manual, possible bugs |
| Immutability | Guaranteed | Need to maintain |
| Performance | Optimal | Depends on implementation |
| Mutable fields | Need to protect | Need to maintain |
Edge Cases
1. Large Record as key:
// hashCode is computed every time — can be slow
public record LargeKey(
String field1, String field2, String field3,
String field4, String field5, String field6
) {}
// For hot paths — cache hashCode
public record CachedHashKey(String value) {
private final int hash;
public CachedHashKey {
hash = value.hashCode();
}
@Override
public int hashCode() {
return hash;
}
}
2. Null in Record:
public record NullableKey(String name) {}
NullableKey k1 = new NullableKey(null);
NullableKey k2 = new NullableKey(null);
k1.equals(k2); // true (Objects.equals handles null correctly)
Performance
Operation | Record key | String key
-------------------|------------|-----------
hashCode() | 15 ns | 10 ns
equals() | 12 ns | 8 ns
HashMap get/put | O(1) | O(1)
For composite keys — slightly more expensive, but O(1) is preserved
Production Experience
Query cache:
public record QueryCacheKey(
String query,
List<Object> params,
int limit,
int offset
) {
public QueryCacheKey {
params = List.copyOf(params);
}
@Override
public int hashCode() {
// Caching for large keys
return Objects.hash(query, params, limit, offset);
}
}
@Service
public class QueryCache {
private final Map<QueryCacheKey, List<User>> cache = new ConcurrentHashMap<>();
public List<User> getOrLoad(String query, List<Object> params, int limit, int offset) {
var key = new QueryCacheKey(query, params, limit, offset);
return cache.computeIfAbsent(key, k -> loadFromDb(k));
}
}
Best Practices
// ✅ Record for composite keys
public record CacheKey(String tenant, String entityType, String id) {}
// ✅ Immutable components
public record SafeKey(List<String> values) {
public SafeKey { values = List.copyOf(values); }
}
// ✅ Use Records as keys in ConcurrentHashMap
Map<CacheKey, Object> cache = new ConcurrentHashMap<>();
// ❌ Mutable components without protection
// ❌ Arrays in key without overriding equals/hashCode
// ❌ Record with many fields (hashCode is expensive)
🎯 Interview Cheat Sheet
Must know:
- Record is a great key for HashMap thanks to auto-generated equals/hashCode
- Record is immutable — key won’t change after being added to Map
- Mutable components (List, array) break hashCode contract — need defensive copy
- Arrays are compared by reference, not by contents — need to override equals/hashCode with Arrays.equals
- For large Records, hashCode can be expensive — can cache it
- Null in components is handled correctly via Objects.equals
Common follow-up questions:
- Why is Record better than regular class as key? — Auto-generation of equals/hashCode, guaranteed immutability
- What happens with mutable component in key? — When changed, hashCode changes — data is lost in HashMap
- Can you use array as key component? — Only with overriding equals/hashCode via Arrays.equals/hashCode
- Is Record hashCode computed every time? — Yes, but can be cached for large keys
Red flags (DO NOT say):
- ❌ “Array in Record compares correctly” — Arrays are compared by reference, not by contents
- ❌ “Mutable list in key is safe” — Changing the list breaks HashMap
- ❌ “Record hashCode is cached automatically” — No, computed every time
- ❌ “hashCode() uses Objects.hash()” — Uses optimized multiplication chain
Related topics:
- [[1. What is Record in Java and since which version are they available]]
- [[5. What methods are automatically generated for a Record]]
- [[9. Are Record fields final]]
- [[14. Can you create an array of generic type]]