App-vNext/Polly · error · ArgumentNullException
Value cannot be null. (Parameter 'ttlStrategy')
Error message
Value cannot be null. (Parameter 'ttlStrategy')
What it means
Thrown by the terminal CacheAsync<TResult>(...) overload when ttlStrategy is null. The TTL strategy decides how long cached values live; a null strategy would leave expiration undefined, so construction is rejected. The provider guard runs first, so this only fires when the provider is valid but the TTL is null.
Source
Thrown at src/Polly/Caching/AsyncCacheTResultSyntax.cs:986
/// <exception cref="ArgumentNullException">Thrown when <paramref name="onCachePut"/> is <see langword="null"/>.</exception>
public static AsyncCachePolicy<TResult> CacheAsync<TResult>(
IAsyncCacheProvider<TResult> cacheProvider,
ITtlStrategy<TResult> ttlStrategy,
Func<Context, string> cacheKeyStrategy,
Action<Context, string> onCacheGet,
Action<Context, string> onCacheMiss,
Action<Context, string> onCachePut,
Action<Context, string, Exception>? onCacheGetError,
Action<Context, string, Exception>? onCachePutError)
{
if (cacheProvider == null)
{
throw new ArgumentNullException(nameof(cacheProvider));
}
if (ttlStrategy == null)
{
throw new ArgumentNullException(nameof(ttlStrategy));
}
if (cacheKeyStrategy == null)
{
throw new ArgumentNullException(nameof(cacheKeyStrategy));
}
if (onCacheGet == null)
{
throw new ArgumentNullException(nameof(onCacheGet));
}
if (onCacheMiss == null)
{
throw new ArgumentNullException(nameof(onCacheMiss));
}
if (onCachePut == null)View on GitHub (pinned to d0e46bdb1e)
Solutions
- Pass a concrete ITtlStrategy<TResult> such as new RelativeTtl(TimeSpan.FromMinutes(5)).
- If you only need a fixed TTL, prefer the TimeSpan-based overload which constructs RelativeTtl for you.
- Null-check the strategy at the call site.
Example fix
// before var policy = Policy.CacheAsync<string>(provider, (ITtlStrategy<string>)null, keyFunc, onGet, onMiss, onPut, null, null); // after var policy = Policy.CacheAsync<string>(provider, new RelativeTtl(TimeSpan.FromMinutes(5)).For<string>(), keyFunc, onGet, onMiss, onPut, null, null);
Defensive patterns
Strategy: validation
Validate before calling
var ttl = ttlStrategy ?? new RelativeTtl(TimeSpan.FromMinutes(5)); var policy = Policy.CacheAsync<TResult>(provider, ttl.For<TResult>(), keyFunc, onGet, onMiss, onPut, null, null);
Type guard
static bool HasTtl<T>(ITtlStrategy<T>? s) => s is not null;
Try / catch
try { var p = Policy.CacheAsync<R>(provider, ttl, keyFunc, onGet, onMiss, onPut, null, null); }
catch (ArgumentNullException ex) when (ex.ParamName == "ttlStrategy") { /* default the TTL or fail */ } Prevention
- Default to RelativeTtl in your builder when no custom TTL is configured.
- Prefer the TimeSpan overload for fixed TTLs.
- Validate config sections that drive TTL construction.
When it happens
Trigger: Calling the full overload (or a public overload that funnels here) with a null ITtlStrategy<TResult>, e.g. passing null where a RelativeTtl or ContextualTtl should be.
Common situations: The TTL strategy field was never assigned; a config-driven TTL factory returned null when the config section was absent; refactor swapped in an overload that no longer wraps TimeSpan into RelativeTtl.
Related errors
- Value cannot be null. (Parameter 'onCacheGet')
- Value cannot be null. (Parameter 'onCacheMiss')
- Value cannot be null. (Parameter 'onCachePut')
- Value cannot be null. (Parameter 'wrappedCacheProvider')
- Value cannot be null. (Parameter 'action')
AI-assisted analysis of App-vNext/Polly@d0e46bdb1e (2026-08-13).
Data as JSON: /api/errors/057c499d0eb7028d.
Report an issue: GitHub.