{"record":{"id":"36ea0d9eea747d84","repo":"TheAlgorithms/Java","slug":"cache-cannot-be-empty","errorCode":null,"errorMessage":"cache cannot be empty!","messagePattern":"cache cannot be empty!","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"src/main/java/com/thealgorithms/datastructures/caches/LRUCache.java","lineNumber":79,"sourceCode":"     * @param newCapacity the new capacity of the cache\n     */\n    private void setCapacity(int newCapacity) {\n        checkCapacity(newCapacity);\n        for (int i = data.size(); i > newCapacity; i--) {\n            Entry<K, V> evicted = evict();\n            data.remove(evicted.getKey());\n        }\n        this.cap = newCapacity;\n    }\n\n    /**\n     * Evicts the least recently used item from the cache.\n     *\n     * @return the evicted entry\n     */\n    private Entry<K, V> evict() {\n        if (head == null) {\n            throw new RuntimeException(\"cache cannot be empty!\");\n        }\n        Entry<K, V> evicted = head;\n        head = evicted.getNextEntry();\n        head.setPreEntry(null);\n        evicted.setNextEntry(null);\n        return evicted;\n    }\n\n    /**\n     * Checks if the capacity is valid.\n     *\n     * @param capacity the capacity to check\n     */\n    private void checkCapacity(int capacity) {\n        if (capacity <= 0) {\n            throw new RuntimeException(\"capacity must greater than 0!\");\n        }\n    }","sourceCodeStart":61,"sourceCodeEnd":97,"githubUrl":"https://github.com/TheAlgorithms/Java/blob/fdfb9a395b310167a66bd29e311e36e0e3e9b964/src/main/java/com/thealgorithms/datastructures/caches/LRUCache.java#L61-L97","documentation":"Internal assertion inside the private evict() method of LRUCache. It fires when eviction is attempted but the doubly-linked list head is null, meaning the cache's linked list is empty while eviction logic expected at least one entry. LRUCache is explicitly documented as thread-unsafe; in correct single-threaded use the caller guards (setCapacity loops on data.size(), put() checks data.size() == cap) prevent evict() being called on an empty list. Hitting this almost always means concurrent access corrupted the internal state or there is a library bug.","triggerScenarios":"Multiple threads calling put() and/or get() on the same LRUCache instance concurrently. The HashMap field 'data' and the linked-list pointers 'head'/'tail' are independent and unsynchronized, so a race can leave data.size() > 0 while head == null; the next put() that triggers eviction then hits the null head. Also reachable if a future code change to setCapacity or put breaks the size/list invariant.","commonSituations":"Wrapping LRUCache behind a web request handler, async pipeline, or message consumer without external synchronization. Treating it as a drop-in replacement for Guava/Caffeine caches that are thread-safe by default.","solutions":["Wrap all put/get access in a synchronized block or ReentrantLock to prevent concurrent state corruption","Replace LRUCache with a thread-safe alternative such as Caffeine, Guava Cache, or ConcurrentHashMap with access-order","If strictly single-threaded and still hitting it, file a bug against the library with a reproducer"],"exampleFix":"// before — shared unsynchronized cache, triggers race\nLRUCache<String,String> cache = new LRUCache<>(100);\n// many threads call cache.put(...) / cache.get(...)\n\n// after — external synchronization\nLRUCache<String,String> cache = new LRUCache<>(100);\nprivate final Object lock = new Object();\npublic String safeGet(String k) {\n    synchronized (lock) { return cache.get(k); }\n}\npublic void safePut(String k, String v) {\n    synchronized (lock) { cache.put(k, v); }\n}","handlingStrategy":"validation","validationCode":"// LRUCache is thread-unsafe; prevent concurrent access by design.\n// Ensure only one thread ever touches the cache instance:\nprivate final Object cacheLock = new Object();\nvoid safePut(LRUCache<K,V> cache, K k, V v) {\n    synchronized (cacheLock) { cache.put(k, v); }\n}\nV safeGet(LRUCache<K,V> cache, K k) {\n    synchronized (cacheLock) { return cache.get(k); }\n}","typeGuard":null,"tryCatchPattern":"// Not recommended as primary defense — fix concurrency instead.\n// If you must catch:\ntry {\n    cache.put(key, val);\n} catch (RuntimeException e) {\n    if (e.getMessage().equals(\"cache cannot be empty!\")) {\n        // internal state corrupted by race; rebuild cache\n        cache = new LRUCache<>(capacity);\n    } else throw e;\n}","preventionTips":["Never share an LRUCache across threads without external synchronization","Document which thread owns the cache instance","Prefer Caffeine or Guava Cache for concurrent environments"],"tags":["lru-cache","concurrency","thread-safety","data-structures"],"backgroundTag":null,"analyzedSha":"fdfb9a395b310167a66bd29e311e36e0e3e9b964","analyzedAt":"2026-08-13T23:36:13.315Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}