{"record":{"id":"73130102b50d1fdd","repo":"dotnet/aspnetcore","slug":"the-absolute-expiration-value-must-be-in-the-futur","errorCode":null,"errorMessage":"The absolute expiration value must be in the future.","messagePattern":"The absolute expiration value must be in the future\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Caching/SqlServer/src/DatabaseOperations.cs","lineNumber":383,"sourceCode":"        {\n            return ex.Errors.Cast<SqlError>().Any(error => error.Number == DuplicateKeyErrorId);\n        }\n        return false;\n    }\n\n    private static DateTimeOffset? GetAbsoluteExpiration(DateTimeOffset utcNow, DistributedCacheEntryOptions options)\n    {\n        // calculate absolute expiration\n        DateTimeOffset? absoluteExpiration = null;\n        if (options.AbsoluteExpirationRelativeToNow.HasValue)\n        {\n            absoluteExpiration = utcNow.Add(options.AbsoluteExpirationRelativeToNow.Value);\n        }\n        else if (options.AbsoluteExpiration.HasValue)\n        {\n            if (options.AbsoluteExpiration.Value <= utcNow)\n            {\n                throw new InvalidOperationException(\"The absolute expiration value must be in the future.\");\n            }\n\n            absoluteExpiration = options.AbsoluteExpiration.Value;\n        }\n        return absoluteExpiration;\n    }\n\n    private static void ValidateOptions(TimeSpan? slidingExpiration, DateTimeOffset? absoluteExpiration)\n    {\n        if (!slidingExpiration.HasValue && !absoluteExpiration.HasValue)\n        {\n            throw new InvalidOperationException(\"Either absolute or sliding expiration needs \" +\n                \"to be provided.\");\n        }\n    }\n}\n","sourceCodeStart":365,"sourceCodeEnd":400,"githubUrl":"https://github.com/dotnet/aspnetcore/blob/294cab2f9b2e03af6b953820c7ab497c3c8b7ad9/src/Caching/SqlServer/src/DatabaseOperations.cs#L365-L400","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before — absolute expiration already in the past\nvar opts = new DistributedCacheEntryOptions\n{\n    AbsoluteExpiration = DateTimeOffset.UtcNow.AddMinutes(-5)\n};\nawait cache.SetAsync(\"key\", value, opts);\n\n// after — use relative-to-now which cannot be in the past\nvar opts = new DistributedCacheEntryOptions\n{\n    AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1)\n};\nawait cache.SetAsync(\"key\", value, opts);","handlingStrategy":"validation","validationCode":"// Guard before calling Set\nif (options.AbsoluteExpiration.HasValue && options.AbsoluteExpiration.Value <= DateTimeOffset.UtcNow)\n{\n    throw new ArgumentOutOfRangeException(nameof(options.AbsoluteExpiration),\n        \"AbsoluteExpiration must be in the future.\");\n}","typeGuard":"static bool IsValidAbsoluteExpiration(DistributedCacheEntryOptions opts)\n    => !opts.AbsoluteExpiration.HasValue || opts.AbsoluteExpiration.Value > DateTimeOffset.UtcNow;","tryCatchPattern":"try\n{\n    await cache.SetAsync(key, value, options);\n}\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"absolute expiration\"))\n{\n    options.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1);\n    await cache.SetAsync(key, value, options);\n}","preventionTips":["Prefer AbsoluteExpirationRelativeToNow over a fixed AbsoluteExpiration to avoid timezone/clock issues.","Add a pre-check that AbsoluteExpiration is in the future before caching."],"tags":["caching","sqlserver","distributed-cache","configuration","expiration"],"analyzedSha":"294cab2f9b2e03af6b953820c7ab497c3c8b7ad9","analyzedAt":"2026-08-06T20:08:02.189Z","schemaVersion":2},"datasetVersion":"2026-08-06T23:17:07.152Z"}