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
- Use AbsoluteExpirationRelativeToNow (TimeSpan) instead of an absolute DateTimeOffset to sidestep clock/timezone issues.
- If you must use AbsoluteExpiration, compute it at the SetAsync call site and ensure it is strictly greater than DateTimeOffset.UtcNow.
- 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
- Prefer AbsoluteExpirationRelativeToNow for Redis cache entries.
- Recompute absolute times at the call site; never reuse stale timestamps.
- Use NTP to keep clocks aligned across cache clients and hosts.
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
- Either absolute or sliding expiration needs to be provided.
- ExpiredItemsDeletionInterval cannot be less than the…
- The absolute expiration value must be in the future.
- The sliding expiration value must be positive.
- ' ' is flagged with SingleDelivery, but the selected…
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)