TheAlgorithms/Java · error · RuntimeException
Cache cannot be empty!
Error message
Cache cannot be empty!
What it means
Internal assertion inside MRUCache's private evict() method. Fires when the most-recently-used entry (tail) is to be evicted but head is null, indicating the linked list is empty. In correct single-threaded operation the caller logic prevents calling evict() on an empty list. Hitting this signals concurrent access on this explicitly thread-unsafe class or a library bug.
Source
Thrown at src/main/java/com/thealgorithms/datastructures/caches/MRUCache.java:78
*
* @param capacity the capacity to check
* @throws IllegalArgumentException if the capacity is less than or equal to zero
*/
private void checkCapacity(int capacity) {
if (capacity <= 0) {
throw new IllegalArgumentException("Capacity must be greater than 0!");
}
}
/**
* Evicts the most recently used entry from the cache.
*
* @return the evicted entry
* @throws RuntimeException if the cache is empty
*/
private Entry<K, V> evict() {
if (head == null) {
throw new RuntimeException("Cache cannot be empty!");
}
final Entry<K, V> evicted = this.tail;
tail = evicted.getPreEntry();
if (tail != null) {
tail.setNextEntry(null);
}
evicted.setNextEntry(null);
return evicted;
}
/**
* Retrieves the value associated with the specified key.
*
* @param key the key whose associated value is to be returned
* @return the value associated with the specified key, or null if the key does not exist
*/
public V get(K key) {
if (!data.containsKey(key)) {View on GitHub (pinned to fdfb9a395b)
Solutions
- Synchronize all access to the MRUCache instance with an external lock
- Switch to a purpose-built thread-safe cache (Caffeine, Guava)
- If single-threaded and still triggering it, report a library bug with a minimal reproducer
Example fix
// before — unsynchronized shared MRUCache
MRUCache<String,byte[]> cache = new MRUCache<>(50);
// concurrent put() calls can corrupt state
// after — guard with a lock
MRUCache<String,byte[]> cache = new MRUCache<>(50);
private final ReentrantLock lock = new ReentrantLock();
public void safePut(String k, byte[] v) {
lock.lock();
try { cache.put(k, v); } finally { lock.unlock(); }
} Defensive patterns
Strategy: validation
Validate before calling
// Prevent concurrent access to the thread-unsafe MRUCache.
private final ReentrantLock mruLock = new ReentrantLock();
void safePut(MRUCache<K,V> cache, K k, V v) {
mruLock.lock();
try { cache.put(k, v); } finally { mruLock.unlock(); }
} Try / catch
try {
cache.put(key, val);
} catch (RuntimeException e) {
if ("Cache cannot be empty!".equals(e.getMessage())) {
cache = new MRUCache<>(cap); // rebuild corrupted state
} else throw e;
} Prevention
- Never share MRUCache across threads without synchronization
- Use a dedicated lock object per cache instance
- Switch to Caffeine or Guava for shared concurrent caches
When it happens
Trigger: Two or more threads simultaneously calling put() on the same MRUCache without external synchronization. The unsynchronized HashMap and linked-list pointers can desynchronize so that data.size() == cap is true while head is null, causing evict() to fail.
Common situations: Using MRUCache as a shared cache in a servlet, actor system, or thread pool without wrapping it in a lock. Assuming thread-safety that the class does not provide.
Related errors
- cache cannot be empty!
- Capacity must be greater than 0!
- Capacity must be greater than zero.
- Cannot enqueue null item.
AI-assisted analysis of TheAlgorithms/Java@fdfb9a395b (2026-08-13).
Data as JSON: /api/errors/fd79adf77e92289a.
Report an issue: GitHub.