TheAlgorithms/Java · error · IllegalArgumentException
TTL must be >= 0
Error message
TTL must be >= 0
What it means
RRCache.put(key, value, ttlMillis) rejects a negative TTL. A negative TTL would set the expiry timestamp in the past, making the entry instantly expired, which is nonsensical. A TTL of 0 means no expiry (entries never expire on their own).
Source
Thrown at src/main/java/com/thealgorithms/datastructures/caches/RRCache.java:168
/**
* Adds a key-value pair to the cache with a specified time-to-live (TTL).
*
* <p>If the key already exists, its value is updated and its TTL is reset. If the key
* does not exist and the cache is full, a random 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 (cache.containsKey(key)) {
cache.put(key, new CacheEntry<>(value, ttlMillis));
return;
}
evictExpired();
if (cache.size() >= capacity) {
int idx = random.nextInt(keys.size());
K evictKey = keys.remove(idx);
CacheEntry<V> evictVal = cache.remove(evictKey);
notifyEviction(evictKey, evictVal.value);
}
View on GitHub (pinned to fdfb9a395b)
Solutions
- Clamp the TTL to Math.max(0, computedTtl) before passing it
- Use 0 explicitly when entries should never expire
- Validate TTL configuration at startup and reject negative values with a clear message
Example fix
// before long ttl = expiryEpoch - System.currentTimeMillis(); // can be negative cache.put(key, value, ttl); // after long ttl = expiryEpoch - System.currentTimeMillis(); cache.put(key, value, Math.max(0, ttl));
Defensive patterns
Strategy: validation
Validate before calling
long safeTtl = Math.max(0, ttlMillis); cache.put(key, value, safeTtl);
Try / catch
try {
cache.put(key, value, ttlMillis);
} catch (IllegalArgumentException e) {
if (ttlMillis < 0) cache.put(key, value, 0); // retry with no-expiry
else throw e;
} Prevention
- Clamp computed TTLs with Math.max(0, value)
- Treat 0 as 'never expires' and use it as the safe default
- Validate duration configs at startup
When it happens
Trigger: Calling cache.put(key, value, -1) or any negative ttlMillis. Also triggered when the TTL is computed from a subtraction or duration conversion that produces a negative result.
Common situations: TTL derived from a config-specified duration that can be negative. Arithmetic on time units (e.g., subtracting a base time) that underflows. Misconfigured or missing TTL property defaulting to a sentinel like -1.
Related errors
- Default TTL must be >= 0
- Interval must be > 0
- Capacity must be > 0
- capacity must greater than 0!
- Capacity must be greater than 0!
AI-assisted analysis of TheAlgorithms/Java@fdfb9a395b (2026-08-13).
Data as JSON: /api/errors/24aef784a76a54e3.
Report an issue: GitHub.