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

  1. Clamp the TTL to Math.max(0, computedTtl) before passing it
  2. Use 0 explicitly when entries should never expire
  3. 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

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


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