Modern applications rarely operate in isolation. They communicate with payment gateways, authentication providers, weather services, AI APIs, databases, and dozens of internal microservices. Unfortunately, these dependencies are not always available. Temporary network failures, rate limits, timeouts, and server errors are common in distributed systems.
If an application immediately fails whenever a downstream service experiences a transient issue, reliability suffers. Polly is a resilience library for .NET that helps applications automatically recover from temporary failures by applying policies such as retries, timeouts, circuit breakers, fallback, and rate limiting.
Rather than writing complex retry logic manually, this article explains how to build resilient HTTP clients in ASP.NET Core using Polly.
Note: Retries should only be used for transient failures. Retrying permanent failures or invalid requests can make problems worse and increase load on downstream services.
Why HTTP Resilience Matters
Without resilience policies, applications may experience:
Resilience patterns help applications recover automatically from temporary failures.
Common Failure Scenarios
Production systems frequently encounter:
These failures are often short-lived and suitable for automated recovery.
Common Polly Policies
| Policy | Purpose |
|---|
| Retry | Retry transient failures |
| Wait and Retry | Retry with configurable delay |
| Circuit Breaker | Stop requests to failing services temporarily |
| Timeout | Cancel slow requests |
| Fallback | Return an alternative response |
| Rate Limiter | Control outgoing request volume |
Multiple policies can be combined to create a comprehensive resilience strategy.
Installing Polly
Install the required NuGet packages.
dotnet add package Microsoft.Extensions.Http.Polly
dotnet add package Polly
These packages integrate Polly with HttpClientFactory.
Registering HttpClient
Configure a typed HTTP client.
builder.Services.AddHttpClient<ProductApiClient>(
client =>
{
client.BaseAddress =
new Uri("https://api.example.com");
});
Using HttpClientFactory prevents socket exhaustion and centralizes client configuration.
Adding a Retry Policy
Configure automatic retries.
builder.Services.AddHttpClient<ProductApiClient>()
.AddPolicyHandler(
HttpPolicyExtensions
.HandleTransientHttpError()
.WaitAndRetryAsync(
3,
retryAttempt =>
TimeSpan.FromSeconds(retryAttempt)));
Transient failures are retried up to three times with increasing delays.
Request Flow with Retry
sequenceDiagram
participant Client
participant API
participant External Service
Client->>API: Request
API->>External Service: HTTP Call
External Service-->>API: HTTP 503
API->>External Service: Retry
External Service-->>API: Success
API-->>Client: Response
The client receives a successful response without knowing a retry occurred.
Adding a Circuit Breaker
Prevent repeated calls to failing services.
.AddPolicyHandler(
HttpPolicyExtensions
.HandleTransientHttpError()
.CircuitBreakerAsync(
handledEventsAllowedBeforeBreaking: 5,
durationOfBreak: TimeSpan.FromSeconds(30)));
Once the failure threshold is reached, the circuit opens and temporarily rejects further requests.
Circuit Breaker States
stateDiagram-v2
[*] --> Closed
Closed --> Open
Open --> HalfOpen
HalfOpen --> Closed
HalfOpen --> Open
The circuit transitions between states based on the health of the downstream service.
Adding a Timeout Policy
Prevent requests from hanging indefinitely.
.AddPolicyHandler(
Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(10)));
Timeouts free application resources when external services become unresponsive.
Combining Multiple Policies
Policies can be composed together.
builder.Services.AddHttpClient<ProductApiClient>()
.AddPolicyHandler(GetRetryPolicy())
.AddPolicyHandler(GetCircuitBreakerPolicy())
.AddPolicyHandler(GetTimeoutPolicy());
Combining policies provides better protection against different types of failures.
Handling HTTP 429 Responses
Many cloud services enforce rate limits.
Example retry policy:
HttpPolicyExtensions
.HandleTransientHttpError()
.OrResult(response =>
response.StatusCode ==
HttpStatusCode.TooManyRequests)
Respecting rate limits improves reliability and prevents unnecessary retries.
Choosing Retry Delays
| Strategy | Best For |
|---|
| Fixed Delay | Simple internal APIs |
| Linear Backoff | Moderate traffic |
| Exponential Backoff | Cloud services |
| Exponential Backoff with Jitter | High-scale distributed systems |
Adding jitter helps prevent many clients from retrying simultaneously.
Common Production Mistakes
| Problem | Root Cause |
|---|
| Service overload | Excessive retries |
| Retry storms | Identical retry intervals |
| Long response times | Missing timeout policy |
| Cascading failures | No circuit breaker |
| Duplicate requests | Retrying non-idempotent operations |
| Hidden failures | Fallbacks masking persistent issues |
Most resilience issues arise from poorly configured policies rather than the Polly library itself.
Best Practices
Use HttpClientFactory for all outbound HTTP calls.
Retry only transient failures.
Prefer exponential backoff with jitter.
Configure reasonable timeout values.
Use circuit breakers for unstable dependencies.
Monitor retry counts and circuit breaker events.
Test resilience policies under simulated failures.
Common Anti-Patterns
Avoid these common mistakes:
Retrying HTTP 400 or other client errors.
Applying unlimited retry attempts.
Ignoring request timeouts.
Using identical retry intervals for every request.
Retrying non-idempotent POST requests without safeguards.
Assuming retries solve every reliability problem.
FAQ
What is Polly?
Polly is a .NET resilience library that provides reusable policies such as retries, circuit breakers, timeouts, and fallbacks for handling transient failures.
Should every HTTP request use retries?
No. Only transient failures should be retried. Permanent failures, such as invalid requests or authentication errors, should fail immediately.
Why is a circuit breaker important?
A circuit breaker prevents repeated requests to an unhealthy service, giving it time to recover while protecting your application from unnecessary delays and resource consumption.
Can Polly be used with Minimal APIs?
Yes. Polly integrates with HttpClientFactory, making it suitable for ASP.NET Core MVC, Web APIs, Minimal APIs, background services, and worker applications.
Conclusion
Reliable communication with external services is a critical requirement for modern ASP.NET Core applications. Polly provides a robust set of resilience patterns that help applications recover from transient failures, reduce downtime, and prevent cascading outages.
By combining retries, timeouts, circuit breakers, and appropriate backoff strategies, you can build HTTP clients that remain responsive even when downstream services experience temporary disruptions. When paired with proper monitoring and observability, Polly becomes an essential part of building production-ready, cloud-native .NET applications.