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 DatabaseOperations.GetAbsoluteExpiration (line 383) during a SqlServerCache Set/SetAsync operation when DistributedCacheEntryOptions.AbsoluteExpiration is set but its value is less than or equal to the current UTC time (utcNow). An absolute expiration in the past or exactly now is rejected because the item would be instantly expired. The exception type is InvalidOperationException.
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 294cab2f9b)
Solutions
- Ensure options.AbsoluteExpiration is set to a future UTC DateTimeOffset: options.AbsoluteExpiration = DateTimeOffset.UtcNow.AddHours(1).
- If you have a relative duration, prefer options.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1) which the library adds to utcNow and never triggers this check.
- Add a guard before calling Set: if (options.AbsoluteExpiration <= DateTimeOffset.UtcNow) throw/recompute.
Example fix
// before — absolute expiration already in the past
var opts = new DistributedCacheEntryOptions
{
AbsoluteExpiration = DateTimeOffset.UtcNow.AddMinutes(-5)
};
await cache.SetAsync("key", value, opts);
// after — use relative-to-now which cannot be in the past
var opts = new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1)
};
await cache.SetAsync("key", value, opts); Defensive patterns
Strategy: validation
Validate before calling
// Guard before calling Set
if (options.AbsoluteExpiration.HasValue && options.AbsoluteExpiration.Value <= DateTimeOffset.UtcNow)
{
throw new ArgumentOutOfRangeException(nameof(options.AbsoluteExpiration),
"AbsoluteExpiration must be in the future.");
} Type guard
static bool IsValidAbsoluteExpiration(DistributedCacheEntryOptions opts)
=> !opts.AbsoluteExpiration.HasValue || opts.AbsoluteExpiration.Value > DateTimeOffset.UtcNow; Try / catch
try
{
await cache.SetAsync(key, value, options);
}
catch (InvalidOperationException ex) when (ex.Message.Contains("absolute expiration"))
{
options.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1);
await cache.SetAsync(key, value, options);
} Prevention
- Prefer AbsoluteExpirationRelativeToNow over a fixed AbsoluteExpiration to avoid timezone/clock issues.
- Add a pre-check that AbsoluteExpiration is in the future before caching.
When it happens
Trigger: SetCacheItem or SetCacheItemAsync is called with options.AbsoluteExpiration set to a DateTimeOffset that is <= SystemClock.UtcNow (line 381 check). This applies to the AbsoluteExpiration property (a fixed DateTimeOffset), NOT AbsoluteExpirationRelativeToNow.
Common situations: Clock skew between the machine computing AbsoluteExpiration and the server; passing a value read from another cache or config that's already passed; computing expiration from a local timezone incorrectly converted to UTC; a bug where DateTime.Now is used instead of a future time.
Related errors
- Either absolute or sliding expiration needs to be provided.
- ExpiredItemsDeletionInterval cannot be less than the minimum
- The sliding expiration value must be positive.
- The absolute expiration value must be in the future.
- Circuit options have already been configured.
AI-assisted analysis of dotnet/aspnetcore@294cab2f9b (2026-08-06).
Data as JSON: /api/errors/73130102b50d1fdd.
Report an issue: GitHub.