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 RedisCache.GetAbsoluteExpiration (line 600) during a Set/SetAsync operation when DistributedCacheEntryOptions.AbsoluteExpiration is set but its value is less than or equal to creationTime (DateTimeOffset.UtcNow). An absolute expiration at or before the current time means the item is immediately expired and is rejected. The exception type is ArgumentOutOfRangeException.

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 294cab2f9b)

Solutions

  1. Use AbsoluteExpirationRelativeToNow instead of a fixed AbsoluteExpiration to avoid clock/timezone issues: options.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1).
  2. If using AbsoluteExpiration, ensure it is a future UTC DateTimeOffset and add a small buffer for clock skew.
  3. Add a pre-check: if (options.AbsoluteExpiration <= DateTimeOffset.UtcNow) recompute or skip caching.

Example fix

// before — fixed absolute time that may be in the past by the time Set runs
var opts = new DistributedCacheEntryOptions
{
    AbsoluteExpiration = somePreviouslyComputedDateTimeOffset
};
await cache.SetStringAsync("key", value, opts);

// after — relative duration, immune to past-time errors
var opts = new DistributedCacheEntryOptions
{
    AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30)
};
await cache.SetStringAsync("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 (ArgumentOutOfRangeException ex) when (ex.Message.Contains("absolute expiration"))
{
    options.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1);
    await cache.SetAsync(key, value, options);
}

Prevention

When it happens

Trigger: RedisCache.SetImpl or SetImplAsync calls GetAbsoluteExpiration (line 595) with options.AbsoluteExpiration <= DateTimeOffset.UtcNow. This applies to the AbsoluteExpiration property (a fixed DateTimeOffset), not AbsoluteExpirationRelativeToNow.

Common situations: Clock skew between the client computing the expiration and the Redis cache host; passing a previously-computed DateTimeOffset that has since passed; incorrect UTC conversion from a local time; deserializing a cached options object whose AbsoluteExpiration is now in the past.

Related errors


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