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

  1. Ensure AbsoluteExpiration is strictly greater than the current UTC time when SetAsync is called.
  2. Prefer AbsoluteExpirationRelativeToNow (a TimeSpan) to avoid timezone/clock-skew mistakes.
  3. If computing AbsoluteExpiration from a captured timestamp, recompute it at the moment of the SetAsync call.
  4. 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

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


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)