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

  1. Synchronize all access to the MRUCache instance with an external lock
  2. Switch to a purpose-built thread-safe cache (Caffeine, Guava)
  3. 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

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


AI-assisted analysis of TheAlgorithms/Java@fdfb9a395b (2026-08-13). Data as JSON: /api/errors/fd79adf77e92289a. Report an issue: GitHub.