dotnet/aspnetcore · error · InvalidOperationException
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 SQL Server distributed cache when DistributedCacheEntryOptions.AbsoluteExpiration is set to a value that is not in the future relative to the current UTC time. GetAbsoluteExpiration rejects past or 'now' expirations because they would yield a cache entry that is already invalid at insert time.
Solutions
- Ensure AbsoluteExpiration is strictly greater than the current UTC time when SetAsync is called.
- Prefer AbsoluteExpirationRelativeToNow (a TimeSpan) to avoid timezone/clock-skew mistakes.
- If computing AbsoluteExpiration from a captured timestamp, recompute it at the moment of the SetAsync call.
- Synchronize clocks (NTP) across servers if skew is the cause.
Example fix
// before: stale timestamp, may be in the past
var opts = new DistributedCacheEntryOptions { AbsoluteExpiration = requestStart.AddMinutes(5) };
await cache.SetStringAsync(key, value, opts);
// after: relative offset computed at call time
var opts = new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5) };
await cache.SetStringAsync(key, value, opts); 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 over absolute DateTimeOffset values.
- Compute expirations at the SetAsync call site, not earlier in the request.
- Sync server clocks via NTP.
When it happens
Trigger: Calling SetAsync with options.AbsoluteExpiration <= utcNow (the cache's SystemClock now). Either the supplied DateTimeOffset is in the past, or it equals now exactly (<=).
Common situations: Computing AbsoluteExpiration from a stale 'now' captured earlier in the request; passing an absolute time sourced from another timezone that resolved to the past; clock skew between the server computing the value and the cache host; reusing a cached expiration value after a delay.
Related errors
- Either absolute or sliding expiration needs to be provided.
- ExpiredItemsDeletionInterval cannot be less than the…
- The sliding expiration value must be positive.
- The absolute expiration value must be in the future.
- ' ' is flagged with SingleDelivery, but the selected…
AI-assisted analysis of dotnet/aspnetcore@3600ca084e (2026-08-11).
Data as JSON: /api/errors/73130102b50d1fdd.
Report an issue: GitHub.
Appendix: source
Thrown at src/Caching/SqlServer/src/DatabaseOperations.cs:383
{
return ex.Errors.Cast<SqlError>().Any(error => error.Number == DuplicateKeyErrorId);
}
return false;
}
private static DateTimeOffset? GetAbsoluteExpiration(DateTimeOffset utcNow, DistributedCacheEntryOptions options)
{
// calculate absolute expiration
DateTimeOffset? absoluteExpiration = null;
if (options.AbsoluteExpirationRelativeToNow.HasValue)
{
absoluteExpiration = utcNow.Add(options.AbsoluteExpirationRelativeToNow.Value);
}
else if (options.AbsoluteExpiration.HasValue)
{
if (options.AbsoluteExpiration.Value <= utcNow)
{
throw new InvalidOperationException("The absolute expiration value must be in the future.");
}
absoluteExpiration = options.AbsoluteExpiration.Value;
}
return absoluteExpiration;
}
private static void ValidateOptions(TimeSpan? slidingExpiration, DateTimeOffset? absoluteExpiration)
{
if (!slidingExpiration.HasValue && !absoluteExpiration.HasValue)
{
throw new InvalidOperationException("Either absolute or sliding expiration needs " +
"to be provided.");
}
}
}
View on GitHub (pinned to 3600ca084e)