TheAlgorithms/Java · error · IllegalArgumentException
TTL must be >= 0
Error message
TTL must be >= 0
What it means
Thrown by LIFOCache.put(K, V, long ttlMillis) when ttlMillis is negative. Expiry is computed as now + ttlMillis, so a negative TTL sets expiry in the past and makes the entry instantly invalid. The check runs after the null guard, before lock acquisition.
Source
Thrown at src/main/java/com/thealgorithms/datastructures/caches/LIFOCache.java:175
/**
* 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 youngest 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. It will later be re-inserted at top of stack
keys.remove(key);
final 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 youngest
if (cache.size() >= capacity) {
final K youngestKey = keys.pollLast();
final CacheEntry<V> youngestEntry = cache.remove(youngestKey);View on GitHub (pinned to fdfb9a395b)
Solutions
- Clamp the TTL: long ttl = Math.max(0, ttlMillis);
- Treat negative TTL as 'no expiry' or 'skip caching' per your domain, explicitly.
- Validate all TTL config keys at startup.
Example fix
// before
cache.put(k, v, duration.toMillis());
// after
long ttl = duration.toMillis();
if (ttl < 0) throw new IllegalStateException("negative ttl: " + 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 TTL config at startup.
- Assert Duration is non-negative before converting to millis.
- Watch for clock-skew arithmetic that inverts the delta.
When it happens
Trigger: cache.put(k, v, -1); ttlMillis from a negative Duration.toMillis(); clock-skew arithmetic yielding a negative delta; config typo with a leading minus.
Common situations: TTL from request headers with bad client clocks; env config (cache.ttl=-5000) typoed; reused duration variable inverted elsewhere.
Related errors
- Default TTL must be >= 0
- TTL must be >= 0
- Default TTL must be >= 0
- Key must not be null
- Key and value must not be null
AI-assisted analysis of TheAlgorithms/Java@fdfb9a395b (2026-08-13).
Data as JSON: /api/errors/33a993957f4fff25.
Report an issue: GitHub.