dotnet/aspnetcore · error · ArgumentOutOfRangeException

The absolute expiration value must be in the future.

Error message

The absolute expiration value must be in the future.

What it means

Thrown by the Redis distributed cache's GetAbsoluteExpiration when AbsoluteExpiration is set and is <= the entry creation time. Unlike the SQL Server variant (228) this throws ArgumentOutOfRangeException. It prevents storing an entry that is already expired at insert.

Solutions

  1. Use AbsoluteExpirationRelativeToNow (TimeSpan) instead of an absolute DateTimeOffset to sidestep clock/timezone issues.
  2. If you must use AbsoluteExpiration, compute it at the SetAsync call site and ensure it is strictly greater than DateTimeOffset.UtcNow.
  3. Synchronize clocks across servers (NTP) to eliminate skew.

Example fix

// before: absolute time captured early
var abs = DateTimeOffset.UtcNow.AddSeconds(1); // could be <= now by the time Set runs
await cache.SetStringAsync(key, value, new DistributedCacheEntryOptions { AbsoluteExpiration = abs });

// after: relative offset
await cache.SetStringAsync(key, value, new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5) });
Defensive patterns

Strategy: validation

Validate before calling

if (options.AbsoluteExpiration.HasValue && options.AbsoluteExpiration.Value <= DateTimeOffset.UtcNow) { throw new ArgumentOutOfRangeException(nameof(options.AbsoluteExpiration)); }

Type guard

static bool AbsoluteExpirationIsValid(DateTimeOffset? abs) => !abs.HasValue || abs.Value > DateTimeOffset.UtcNow;

Prevention

When it happens

Trigger: Calling SetAsync with options.AbsoluteExpiration <= creationTime (the moment the cache started building the entry). This happens when the absolute time is in the past or exactly equal to now.

Common situations: Capturing 'now' early and passing a derived absolute time after a delay; clock skew between client and Redis host; timezone mishandling converting to UTC; reusing a cached DateTimeOffset from an earlier request.

Related errors


AI-assisted analysis of dotnet/aspnetcore@3600ca084e (2026-08-11). Data as JSON: /api/errors/f8a6168dc8ccd9dd. Report an issue: GitHub.

Appendix: source

Thrown at src/Caching/StackExchangeRedis/src/RedisCache.cs:600

                options.SlidingExpiration.Value.TotalSeconds);
        }
        else if (absoluteExpiration.HasValue)
        {
            return (long)(absoluteExpiration.Value - creationTime).TotalSeconds;
        }
        else if (options.SlidingExpiration.HasValue)
        {
            return (long)options.SlidingExpiration.Value.TotalSeconds;
        }
        return null;
    }

    private static DateTimeOffset? GetAbsoluteExpiration(DateTimeOffset creationTime, DistributedCacheEntryOptions options)
    {
        if (options.AbsoluteExpiration.HasValue && options.AbsoluteExpiration <= creationTime)
        {
#pragma warning disable CA2208 // Instantiate argument exceptions correctly
            throw new ArgumentOutOfRangeException(
                nameof(DistributedCacheEntryOptions.AbsoluteExpiration),
                options.AbsoluteExpiration.Value,
                "The absolute expiration value must be in the future.");
#pragma warning restore CA2208 // Instantiate argument exceptions correctly
        }

        if (options.AbsoluteExpirationRelativeToNow.HasValue)
        {
            return creationTime + options.AbsoluteExpirationRelativeToNow;
        }

        return options.AbsoluteExpiration;
    }

    /// <inheritdoc />
    public void Dispose()
    {
        if (_disposed)

View on GitHub (pinned to 3600ca084e)