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

  1. Pass a concrete ITtlStrategy<TResult> such as new RelativeTtl(TimeSpan.FromMinutes(5)).
  2. If you only need a fixed TTL, prefer the TimeSpan-based overload which constructs RelativeTtl for you.
  3. 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

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


AI-assisted analysis of App-vNext/Polly@d0e46bdb1e (2026-08-13). Data as JSON: /api/errors/057c499d0eb7028d. Report an issue: GitHub.