ASP.NET Core  

Building Resilient HTTP Clients in ASP.NET Core with Polly

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:

  • Failed API requests

  • Cascading service failures

  • Poor user experience

  • Increased downtime

  • Resource exhaustion

  • Unstable microservices

Resilience patterns help applications recover automatically from temporary failures.

Common Failure Scenarios

Production systems frequently encounter:

  • Network interruptions

  • DNS resolution failures

  • HTTP 500 responses

  • HTTP 503 Service Unavailable

  • HTTP 429 Too Many Requests

  • Request timeouts

  • Temporary cloud outages

  • Slow downstream services

These failures are often short-lived and suitable for automated recovery.

Common Polly Policies

PolicyPurpose
RetryRetry transient failures
Wait and RetryRetry with configurable delay
Circuit BreakerStop requests to failing services temporarily
TimeoutCancel slow requests
FallbackReturn an alternative response
Rate LimiterControl 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

StrategyBest For
Fixed DelaySimple internal APIs
Linear BackoffModerate traffic
Exponential BackoffCloud services
Exponential Backoff with JitterHigh-scale distributed systems

Adding jitter helps prevent many clients from retrying simultaneously.

Common Production Mistakes

ProblemRoot Cause
Service overloadExcessive retries
Retry stormsIdentical retry intervals
Long response timesMissing timeout policy
Cascading failuresNo circuit breaker
Duplicate requestsRetrying non-idempotent operations
Hidden failuresFallbacks 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.