removeEldestEntry(Map.Entry eldest) is a protected hook that LinkedHashMap calls after every put (and putAll), passing the current head of the access chain. If you return true, that entry is evicted before put returns. The default returns false, so a plain LinkedHashMap never evicts.
The signature
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
return false;
}
When it fires
Inside LinkedHashMap.afterNodeInsertion:
void afterNodeInsertion(boolean evict) {
Entry<K,V> first;
if (evict && (first = head) != null && removeEldestEntry(first)) {
K key = first.key;
removeNode(hash(key), key, null, false, true);
}
}
evict is true for put/putAll but false when entries are loaded by readObject during deserialisation — you don't want to evict half your map while restoring it from disk.
Why a hook at all?
LinkedHashMap doesn't want to bake a single eviction policy into the class. Different callers want different rules — size cap, weight cap, time-based, condition-based. By exposing the decision as a hook with the head entry in scope, the class stays simple and the subclass picks the policy:
// Size-based cap
@Override protected boolean removeEldestEntry(Map.Entry<K, V> e) {
return size() > capacity;
}
// Memory-based cap (rough)
@Override protected boolean removeEldestEntry(Map.Entry<K, V> e) {
return Runtime.getRuntime().freeMemory() < threshold;
}
// Time-based: evict if the eldest is older than 5 minutes
@Override protected boolean removeEldestEntry(Map.Entry<K, V> e) {
return e.getValue() instanceof Stamped<?> s
&& s.insertedAt().isBefore(Instant.now().minus(Duration.ofMinutes(5)));
}
Gotchas
- Called once per put. If you insert in a tight loop and need to evict many entries (say, the cap was lowered at runtime), you'll need to loop yourself —
removeEldestEntryonly ever removes one entry perput. - You can ignore the argument. The
eldestparameter is provided for inspection; whether you returntruebased on it or on global state (likesize()) is up to you. - Don't mutate the map inside it. Returning
trueis the only sanctioned side-effect; modifying the map directly from within the hook risksConcurrentModificationExceptionor corrupting the chain.
Listening to evictions
There's no built-in listener for eviction. If you need one, log inside removeEldestEntry before returning true:
@Override protected boolean removeEldestEntry(Map.Entry<K, V> e) {
boolean evict = size() > capacity;
if (evict) log.debug("evicting {}", e.getKey());
return evict;
}