Polly v8 is a complete rewrite. The old Policy and PolicyWrap APIs are gone, replaced by a unified ResiliencePipeline that composes strategies in a consistent, performant way. If you've used Polly before, the concepts are the same — retry, circuit breaker, timeout, fallback — but the API is entirely different.
Installation
dotnet add package Microsoft.Extensions.Resilience
dotnet add package Microsoft.Extensions.Http.Resilience
The Microsoft.Extensions.Resilience package wraps Polly v8 with first-class DI integration. The HTTP package adds HttpClient resilience specifically.
Building a Resilience Pipeline
Strategies are composed into a pipeline using ResiliencePipelineBuilder:
var pipeline = new ResiliencePipelineBuilder()
.AddRetry(new RetryStrategyOptions
{
MaxRetryAttempts = 3,
Delay = TimeSpan.FromMilliseconds(500),
BackoffType = DelayBackoffType.Exponential,
ShouldHandle = new PredicateBuilder().Handle<HttpRequestException>()
})
.AddTimeout(TimeSpan.FromSeconds(10))
.Build();
Execute through the pipeline:
var result = await pipeline.ExecuteAsync(async ct =>
{
return await httpClient.GetStringAsync("/api/data", ct);
}, cancellationToken);
DI Registration
Register named pipelines in the service collection:
builder.Services.AddResiliencePipeline("default", builder =>
{
builder
.AddRetry(new RetryStrategyOptions
{
MaxRetryAttempts = 3,
Delay = TimeSpan.FromSeconds(1),
BackoffType = DelayBackoffType.Exponential,
UseJitter = true
})
.AddCircuitBreaker(new CircuitBreakerStrategyOptions
{
FailureRatio = 0.5,
SamplingDuration = TimeSpan.FromSeconds(30),
MinimumThroughput = 10,
BreakDuration = TimeSpan.FromSeconds(30)
})
.AddTimeout(TimeSpan.FromSeconds(15));
});
Inject and use:
public class ExternalApiClient
{
private readonly ResiliencePipeline _pipeline;
private readonly HttpClient _httpClient;
public ExternalApiClient(
ResiliencePipelineProvider<string> pipelineProvider,
HttpClient httpClient)
{
_pipeline = pipelineProvider.GetPipeline("default");
_httpClient = httpClient;
}
public async Task<OrderStatus> GetStatusAsync(
string orderId, CancellationToken ct)
{
return await _pipeline.ExecuteAsync(async token =>
{
var response = await _httpClient
.GetFromJsonAsync<OrderStatus>($"/orders/{orderId}", token);
return response!;
}, ct);
}
}
HttpClient Resilience
The most common use case is resilient HTTP calls. The Microsoft.Extensions.Http.Resilience package provides a streamlined API:
builder.Services.AddHttpClient<ExternalApiClient>(client =>
{
client.BaseAddress = new Uri("https://api.example.com");
})
.AddStandardResilienceHandler();
AddStandardResilienceHandler adds a sensible default pipeline: rate limiter, total timeout, retry with exponential backoff, circuit breaker, and per-attempt timeout. You can customise any part:
builder.Services.AddHttpClient<ExternalApiClient>(client =>
{
client.BaseAddress = new Uri("https://api.example.com");
})
.AddStandardResilienceHandler(options =>
{
options.Retry.MaxRetryAttempts = 5;
options.Retry.Delay = TimeSpan.FromSeconds(2);
options.CircuitBreaker.BreakDuration = TimeSpan.FromMinutes(1);
options.AttemptTimeout.Timeout = TimeSpan.FromSeconds(5);
options.TotalRequestTimeout.Timeout = TimeSpan.FromSeconds(30);
});
Individual Strategies
Retry with Backoff and Jitter
builder.AddRetry(new RetryStrategyOptions
{
MaxRetryAttempts = 3,
Delay = TimeSpan.FromSeconds(1),
BackoffType = DelayBackoffType.Exponential,
UseJitter = true,
OnRetry = args =>
{
logger.LogWarning(
"Retry {AttemptNumber} after {Duration}ms",
args.AttemptNumber,
args.RetryDelay.TotalMilliseconds);
return ValueTask.CompletedTask;
}
});
Always enable jitter in production — without it, retries from multiple instances hit the downstream service at the same instant.
Circuit Breaker
builder.AddCircuitBreaker(new CircuitBreakerStrategyOptions
{
FailureRatio = 0.5,
SamplingDuration = TimeSpan.FromSeconds(30),
MinimumThroughput = 10,
BreakDuration = TimeSpan.FromSeconds(30),
OnOpened = args =>
{
logger.LogError("Circuit opened for {Duration}", args.BreakDuration);
return ValueTask.CompletedTask;
}
});
The circuit opens when 50% of at least 10 requests in a 30-second window fail. Once open, all requests fail immediately for 30 seconds before the circuit tries half-open.
Fallback
var pipeline = new ResiliencePipelineBuilder<OrderStatus>()
.AddFallback(new FallbackStrategyOptions<OrderStatus>
{
ShouldHandle = new PredicateBuilder<OrderStatus>()
.Handle<HttpRequestException>()
.Handle<TimeoutRejectedException>(),
FallbackAction = _ => Outcome.FromResultAsValueTask(
new OrderStatus { Status = "Unknown" })
})
.AddRetry(new RetryStrategyOptions<OrderStatus>
{
MaxRetryAttempts = 3
})
.Build();
Strategy Order Matters
Strategies execute in the order you add them. The outermost strategy runs first. A common pattern is:
- Total timeout — caps the entire operation
- Retry — retries failed attempts
- Circuit breaker — stops calling a failing dependency
- Attempt timeout — caps each individual attempt
This means the total timeout wraps everything, the retry wraps the circuit breaker, and the circuit breaker wraps each attempt.
Polly v8's pipeline model makes resilience composable and testable. The Microsoft.Extensions.Http.Resilience integration means you can add production-grade fault tolerance to HTTP calls with a single method call.