App-vNext/Polly · error · InvalidOperationException

You have executed the generic .Execute<TResult> method on a

Error message

You have executed the generic .Execute<TResult> method on a non-generic FallbackPolicy.  A non-generic FallbackPolicy only defines a fallback action which returns void; it can never return a substitute TResult value.  To use FallbackPolicy to provide fallback TResult values you must define a generic fallback policy FallbackPolicy<TResult>.  For example, define the policy as Policy<TResult>.Handle<Whatever>.Fallback<TResult>(/* some TResult value or Func<..., TResult> */);

What it means

Thrown when code calls the generic Execute<TResult>/ExecuteAsync<TResult> path on a NON-generic AsyncFallbackPolicy. A non-generic FallbackPolicy (Policy.Handle<...>.Fallback(...)) only defines a void fallback action; it cannot synthesize a substitute TResult, so the framework rejects the call with InvalidOperationException at execution time. To return fallback values you must build a generic FallbackPolicy<TResult> via Policy<TResult>.Handle<...>.Fallback<TResult>(...).

Source

Thrown at src/Polly/Fallback/AsyncFallbackPolicy.cs:49

                await action(ctx, ct).ConfigureAwait(continueOnCapturedContext);
                return EmptyStruct.Instance;
            },
            context,
            ExceptionPredicates,
            ResultPredicates<EmptyStruct>.None,
            (outcome, ctx) => _onFallbackAsync(outcome.Exception, ctx),
            async (outcome, ctx, ct) =>
            {
                await _fallbackAction(outcome.Exception, ctx, ct).ConfigureAwait(continueOnCapturedContext);
                return EmptyStruct.Instance;
            },
            continueOnCapturedContext,
            cancellationToken);

    /// <inheritdoc/>
    protected override Task<TResult> ImplementationAsync<TResult>(Func<Context, CancellationToken, Task<TResult>> action, Context context, CancellationToken cancellationToken,
        bool continueOnCapturedContext) =>
        throw new InvalidOperationException($"You have executed the generic .Execute<{nameof(TResult)}> method on a non-generic {nameof(FallbackPolicy)}.  " +
            $"A non-generic {nameof(FallbackPolicy)} only defines a fallback action which returns void; it can never return a substitute {nameof(TResult)} value.  " +
            $"To use {nameof(FallbackPolicy)} to provide fallback {nameof(TResult)} values you must define a generic fallback policy {nameof(FallbackPolicy)}<{nameof(TResult)}>.  " +
            $"For example, define the policy as Policy<{nameof(TResult)}>.Handle<Whatever>.Fallback<{nameof(TResult)}>(/* some {nameof(TResult)} value or Func<..., {nameof(TResult)}> */);");
}

/// <summary>
/// A fallback policy that can be applied to delegates.
/// </summary>
/// <typeparam name="TResult">The return type of delegates which may be executed through the policy.</typeparam>
public class AsyncFallbackPolicy<TResult> : AsyncPolicy<TResult>, IFallbackPolicy<TResult>
{
    private readonly Func<DelegateResult<TResult>, Context, Task> _onFallbackAsync;
    private readonly Func<DelegateResult<TResult>, Context, CancellationToken, Task<TResult>> _fallbackAction;

    internal AsyncFallbackPolicy(
        PolicyBuilder<TResult> policyBuilder,
        Func<DelegateResult<TResult>, Context, Task> onFallbackAsync,
        Func<DelegateResult<TResult>, Context, CancellationToken, Task<TResult>> fallbackAction)

View on GitHub (pinned to d0e46bdb1e)

Solutions

  1. Define the policy generically: Policy<TResult>.Handle<Whatever>().Fallback<TResult>(fallbackValue or fallbackAction, onFallback).
  2. Match the Execute call's TResult to the policy's TResult; use a non-generic Execute only with a non-generic FallbackPolicy.
  3. If you truly want a void fallback, keep using non-generic Execute (not Execute<TResult>).

Example fix

// before
var policy = Policy
    .Handle<HttpRequestException>()
    .FallbackAsync(async (ex, ctx, ct) => { /* void fallback */ });
var value = await policy.ExecuteAsync<int>(ct => GetValueAsync(ct), ct); // throws

// after
var policy = Policy<int>
    .Handle<HttpRequestException>()
    .FallbackAsync(fallbackValue: 0, onFallbackAsync: (outcome, ctx) => Task.CompletedTask);
var value = await policy.ExecuteAsync(ct => GetValueAsync(ct), ct);
Defensive patterns

Strategy: type-guard

Validate before calling

// choose the policy arity at compile time to match the result
var policy = Policy<int>.Handle<HttpRequestException>()
    .FallbackAsync(fallbackValue: 0, onFallbackAsync: (o, ctx) => Task.CompletedTask);
var v = await policy.ExecuteAsync(ct => GetAsync(ct), ct);

Type guard

// structural check before executing
static bool IsResultFallbackPolicy(object p) => p is IFallbackPolicy<int>;

Try / catch

try { return await policy.ExecuteAsync<int>(action, ct); } catch (InvalidOperationException ex) when (ex.Message.Contains("non-generic FallbackPolicy")) { log.PolicyArityMismatch(ex); throw; }

Prevention

When it happens

Trigger: Building the policy as Policy.Handle<X>().Fallback(...) (non-generic) and then calling ExecuteAsync<TResult>(...) or Execute<TResult>(...) on it, expecting it to yield a typed fallback value.

Common situations: Refactoring a void delegate policy into one that returns results but forgetting to switch Policy to Policy<TResult> and to the generic Fallback overload; copy-paste from a void tutorial into a value-returning handler.

Related errors


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