How can the Polly resilience framework be used in .NET applications to handle retries, timeouts, circuit breakers, and transient failures?
Question
Answers
A production application cannot assume that every dependency will always respond successfully.
External APIs can experience:
- Temporary network failures
- HTTP 429 responses
- Timeouts
- Service interruptions
- Slow responses
- Connection failures
- Temporary database or service unavailability
A resilient application should distinguish between a transient failure and a permanent failure.
Polly is a widely used .NET resilience and transient-fault-handling library. Microsoft describes it as supporting strategies including retry, circuit breaker, timeout, rate limiting, fallback and hedging. Current Microsoft guidance uses the Microsoft.Extensions.Resilience and Microsoft.Extensions.Http.Resilience packages, which are built on Polly; the older Microsoft.Extensions.Http.Polly package is deprecated.
Retry
Retry means:
"The operation failed, but the failure may be temporary, so try again."
For example:
API Call ↓ Failure ↓ Wait ↓ Retry ↓ Success
Retries are appropriate for selected transient failures.
They should not blindly retry every exception.
For example:
401 Unauthorized
usually requires an authentication/configuration correction, not repeated retries.
Similarly, a validation error such as:
400 Bad Request
normally should not be retried unchanged.
Exponential backoff
A common strategy is to progressively increase the delay:
Attempt 1 → immediate Attempt 2 → short delay Attempt 3 → longer delay Attempt 4 → longer delay
This prevents thousands of clients from repeatedly hitting an already unhealthy service.
For distributed systems, jitter can also help avoid synchronized retry storms.
Timeout
A timeout establishes:
"How long are we willing to wait?"
Without timeouts, an application may accumulate blocked requests:
Request 1 → waiting Request 2 → waiting Request 3 → waiting Request 4 → waiting ...
Eventually, application resources can become exhausted.
A timeout creates an explicit boundary.
Circuit breaker
The circuit breaker is one of the most important resilience concepts.
Imagine an external payment service is unavailable.
Without a circuit breaker:
Application ↓ Payment API ↓ Failure Application ↓ Payment API ↓ Failure Application ↓ Payment API ↓ Failure
The application keeps hammering the unhealthy dependency.
With a circuit breaker:
Normal ↓ Failures increase ↓ Circuit opens ↓ Calls rejected quickly ↓ Recovery period ↓ Test request ↓ Circuit closes if healthy
This protects both systems.
Resilience strategies work together
Modern .NET resilience pipelines can combine multiple strategies.
Microsoft's current HTTP standard resilience configuration includes strategies such as rate limiting/bulkhead protection, total request timeout, retry, circuit breaker and per-attempt timeout. The order in which resilience strategies are configured matters.
Conceptually:
Incoming Request ↓ Concurrency / Rate Protection ↓ Total Timeout ↓ Retry ↓ Circuit Breaker ↓ Attempt Timeout ↓ External API
The important lesson is:
Resilience is a system design, not simply "add retry".
Retry can actually make an outage worse
This is a critical production consideration.
Suppose 10,000 requests fail simultaneously.
If every request retries three times immediately:
10,000 original requests + 30,000 retries = 40,000 requests
The dependency may become even more overloaded.
Therefore, enterprise retry design should consider:
- Which errors are transient
- Maximum retry count
- Backoff
- Jitter
- Request idempotency
- Rate limiting
- Timeout
- Circuit breaking
- Dependency capacity
Idempotency matters
Consider:
POST /payments
If the server processed the payment but the response was lost, a client might retry.
That could potentially create a duplicate operation.
Therefore, developers must understand whether the operation is safe to retry.
Resilience mechanisms cannot compensate for a fundamentally non-idempotent integration design.
Practical example
Imagine a .NET API calling an external employee service:
.NET API ↓ HttpClient ↓ Resilience Pipeline ↓ Employee API
The design could apply:
- Timeout for slow requests
- Retry for appropriate transient failures
- Circuit breaker for repeated failures
- Rate/concurrency protection
- Logging and telemetry
The goal is not:
"Never fail."
The goal is:
Fail predictably, recover where possible, and protect the application and its dependencies when recovery is not possible.