Transient Fault Handling with Polly in C

This article provides a practical guide on implementing resilience and transient fault handling in C# applications using the Polly library. You will learn the core concepts of transient errors, how to install and configure modern Polly (v8+) resilience pipelines, and how to implement essential patterns including Retry with exponential backoff, Circuit Breakers, and Fallbacks, as well as integrating them with .NET's IHttpClientFactory.

Understanding Transient Faults and Polly

Transient faults are temporary errors—such as brief network drops, momentary service unavailability, or HTTP 503 Service Unavailable responses—that typically resolve themselves quickly. Instead of letting an application fail immediately when encountering these issues, resilience frameworks allow operations to be retried or rerouted gracefully.

Polly is the standard .NET resilience and transient fault-handling library. Starting with Polly v8, the library uses a unified ResiliencePipeline architecture designed for high performance, zero allocations, and seamless integration with the modern .NET ecosystem.

Installing Polly

To get started, install the Polly core package via the .NET CLI:

dotnet add package Polly.Core

If you are working with HttpClient in ASP.NET Core or generic hosts, install the official Microsoft integration package:

dotnet add package Microsoft.Extensions.Http.Resilience

Creating a Resilience Pipeline

In modern Polly, resilience strategies are configured and executed through a ResiliencePipelineBuilder.

1. The Retry Strategy

The Retry strategy automatically re-executes a failed action. Using exponential backoff with jitter prevents overwhelming a recovering downstream service (the "thundering herd" problem).

using Polly;
using Polly.Retry;

var pipeline = new ResiliencePipelineBuilder()
    .AddRetry(new RetryStrategyOptions
    {
        ShouldHandle = new PredicateBuilder().Handle<HttpRequestException>(),
        MaxRetryAttempts = 3,
        Delay = TimeSpan.FromSeconds(1),
        BackoffType = DelayBackoffType.Exponential,
        UseJitter = true
    })
    .Build();

// Executing code through the pipeline
await pipeline.ExecuteAsync(async cancellationToken =>
{
    await CallExternalServiceAsync(cancellationToken);
});

2. The Circuit Breaker Strategy

The Circuit Breaker pattern prevents an application from repeatedly attempting an operation that is almost certain to fail. When failures exceed a defined threshold, the circuit "opens," failing immediately for a specified duration before allowing test traffic through again.

using Polly;
using Polly.CircuitBreaker;

var pipeline = new ResiliencePipelineBuilder()
    .AddCircuitBreaker(new HttpCircuitBreakerStrategyOptions
    {
        FailureRatio = 0.5, // Break if 50% of requests fail
        SamplingDuration = TimeSpan.FromSeconds(30),
        MinimumThroughput = 10,
        BreakDuration = TimeSpan.FromSeconds(15)
    })
    .Build();

3. The Fallback Strategy

The Fallback strategy substitutes an alternative value or action when an execution fails, ensuring that user flows are not entirely disrupted.

using Polly;
using Polly.Fallback;

var pipeline = new ResiliencePipelineBuilder<string>()
    .AddFallback(new FallbackStrategyOptions<string>
    {
        ShouldHandle = new PredicateBuilder<string>().Handle<Exception>(),
        FallbackAction = static args => Outcome.FromResultAsValueTask("Default cached data")
    })
    .Build();

string result = await pipeline.ExecuteAsync(async cancellationToken =>
{
    return await FetchUnstableDataAsync(cancellationToken);
});

Combining Strategies into a Composite Pipeline

Resilience strategies are most effective when combined. The standard order of operations is typically: Fallback (outermost) -> Total Timeout -> Retry -> Circuit Breaker -> Attempt Timeout (innermost).

using Polly;
using Polly.Timeout;
using Polly.Retry;
using Polly.CircuitBreaker;

var pipeline = new ResiliencePipelineBuilder()
    .AddTimeout(TimeSpan.FromSeconds(30)) // Overall operation timeout
    .AddRetry(new RetryStrategyOptions
    {
        ShouldHandle = new PredicateBuilder().Handle<HttpRequestException>(),
        MaxRetryAttempts = 3,
        BackoffType = DelayBackoffType.Exponential
    })
    .AddCircuitBreaker(new CircuitBreakerStrategyOptions
    {
        FailureRatio = 0.3,
        SamplingDuration = TimeSpan.FromSeconds(10),
        BreakDuration = TimeSpan.FromSeconds(5)
    })
    .AddTimeout(TimeSpan.FromSeconds(5)) // Timeout per attempt
    .Build();

Integration with HttpClient in ASP.NET Core

For modern .NET web applications, the Microsoft.Extensions.Http.Resilience package provides pre-configured standard pipelines that apply retries, circuit breakers, and timeouts automatically to HTTP requests.

In Program.cs:

using Microsoft.Extensions.DependencyInjection;

var builder = WebApplication.CreateBuilder(args);

// Adds an HttpClient with recommended resilience defaults:
// Retries, Circuit Breaker, Total and Attempt Timeouts
builder.Services.AddHttpClient("ExternalService")
    .AddStandardResilienceHandler(options =>
    {
        options.Retry.MaxRetryAttempts = 3;
        options.Retry.BackoffType = DelayBackoffType.Exponential;
        options.CircuitBreaker.SamplingDuration = TimeSpan.FromSeconds(10);
    });

var app = builder.Build();

To consume this client, inject IHttpClientFactory into your services:

public class ApiConsumer
{
    private readonly HttpClient _client;

    public ApiConsumer(IHttpClientFactory httpClientFactory)
    {
        _client = httpClientFactory.CreateClient("ExternalService");
    }

    public async Task<string> GetDataAsync(CancellationToken cancellationToken)
    {
        var response = await _client.GetAsync("https://api.example.com/data", cancellationToken);
        response.EnsureSuccessStatusCode();
        return await response.Content.ReadAsStringAsync(cancellationToken);
    }
}

By setting up these resilience strategies, your C# application will automatically absorb transient disruptions, isolate downstream outages, and ensure consistent availability.