TheAlgorithms/Java · error · IllegalArgumentException
TTL must be >= 0
Error message
TTL must be >= 0
What it means
Thrown by FIFOCache.put(K, V, long ttlMillis) when ttlMillis is negative. The cache stores an absolute expiry timestamp computed as now + ttlMillis; a negative TTL would set expiry in the past, making the entry instantly invalid and wasting an eviction cycle. The check runs after the null-key/value guard but before lock acquisition.
Source
Thrown at src/main/java/com/thealgorithms/datastructures/caches/FIFOCache.java:169
/**
* Adds a key-value pair to the cache with a specified time-to-live (TTL).
*
* <p>If the key already exists, its value is removed, re-inserted at tail and its TTL is reset.
* If the key does not exist and the cache is full, the oldest entry is evicted to make space.
* Expired entries are also cleaned up prior to any eviction. The eviction listener
* is notified when an entry gets evicted.
*
* @param key the key to associate with the cached value; must not be {@code null}
* @param value the value to be cached; must not be {@code null}
* @param ttlMillis the time-to-live for this entry in milliseconds; must be >= 0
* @throws IllegalArgumentException if {@code key} or {@code value} is {@code null}, or if {@code ttlMillis} is negative
*/
public void put(K key, V value, long ttlMillis) {
if (key == null || value == null) {
throw new IllegalArgumentException("Key and value must not be null");
}
if (ttlMillis < 0) {
throw new IllegalArgumentException("TTL must be >= 0");
}
lock.lock();
try {
// If key already exists, remove it
CacheEntry<V> oldEntry = cache.remove(key);
if (oldEntry != null && !oldEntry.isExpired()) {
notifyEviction(key, oldEntry.value);
}
// Evict expired entries to make space for new entry
evictExpired();
// If no expired entry was removed, remove the oldest
if (cache.size() >= capacity) {
Iterator<Map.Entry<K, CacheEntry<V>>> it = cache.entrySet().iterator();
if (it.hasNext()) {
Map.Entry<K, CacheEntry<V>> eldest = it.next();View on GitHub (pinned to fdfb9a395b)
Solutions
- Clamp or validate the TTL before calling put: long ttl = Math.max(0, ttlMillis);
- Treat negative TTL as 'no expiry' or 'do not cache' per your domain policy, explicitly.
- Validate config values at startup and fail fast with a clear message.
Example fix
// before
cache.put(k, v, duration.toMillis());
// after
long ttl = duration.toMillis();
if (ttl < 0) throw new IllegalStateException("negative ttl from " + duration);
cache.put(k, v, ttl); Defensive patterns
Strategy: validation
Validate before calling
long ttl = Math.max(0, ttlMillis); cache.put(key, value, ttl);
Type guard
static boolean isValidTtl(long ttlMillis) {
return ttlMillis >= 0;
} Prevention
- Validate all TTL config keys at startup and fail fast.
- When deriving TTL from a Duration, assert the Duration is non-negative.
- Beware clock-skew arithmetic that can invert end - start.
When it happens
Trigger: cache.put(k, v, -1); passing a Duration.toMillis() of a negative Duration; computing TTL from a clock skew where end - start goes negative; config typo supplying a negative number.
Common situations: TTL derived from request headers with bad client clocks; environment config (e.g. -Dcache.ttl=-5000) typoed with a leading minus; reusing a duration variable that was inverted elsewhere.
Related errors
- Default TTL must be >= 0
- Key must not be null
- Key and value must not be null
- Key cannot be null
- Interval must be > 0
AI-assisted analysis of TheAlgorithms/Java@fdfb9a395b (2026-08-13).
Data as JSON: /api/errors/5314a6178ac842ed.
Report an issue: GitHub.