Skip to main content

Migration Guide from v7 to v8

Welcome to the migration guide for Polly’s v8 release. Version 8 of Polly brings major new enhancements and supports all of the same scenarios as previous versions. In the following sections, we’ll detail the differences between the v7 and v8 APIs, and provide steps on how to transition smoothly.
The v7 API is still available and fully supported even when using the v8 version by referencing the Polly package. See v7 Compatibility for more details.

Major Differences

The term Policy is now replaced with Strategy

In previous versions, Polly used the term policy for retries, timeouts, etc. In v8, these are referred to as resilience strategies.

Introduction of Resilience Pipelines

A resilience pipeline combines one or more resilience strategies. This is the foundational API for Polly v8, similar to the Policy Wrap in previous versions but integrated into the core API.

Unified sync and async flows

Interfaces such as IAsyncPolicy, IAsyncPolicy<T>, ISyncPolicy, ISyncPolicy<T>, and IPolicy are now unified under ResiliencePipeline and ResiliencePipeline<T>. The resilience pipeline supports both synchronous and asynchronous execution flows.

Native async support

Polly v8 was designed with asynchronous support from the start.

No static APIs

Unlike previous versions, v8 doesn’t use static APIs. This improves testability and extensibility while maintaining ease of use.

Options-based configuration

Configuring individual resilience strategies is now options-based, offering more flexibility and improving maintainability and extensibility.

Built-in telemetry

Polly v8 now has built-in telemetry support.

Improved performance and low-allocation APIs

Polly v8 brings significant performance enhancements and provides zero-allocation APIs for advanced use cases.

Migration Process

When you do your migration process it is recommended to follow these steps:
  1. Upgrade the Polly package version from 7.x to 8.x
    • Your previous policies should run smoothly without any change
  2. Migrate your V7 policies to V8 strategies gradually, such as one at a time
    • Test your migrated code thoroughly
  3. After you have successfully migrated all your legacy Polly code then change your package reference from Polly to Polly.Core

Migrating Execution Policies

This section describes how to migrate from execution policies to resilience pipelines.
// Create and use the ISyncPolicy
ISyncPolicy syncPolicy = Policy
    .Handle<Exception>()
    .WaitAndRetry(3, _ => TimeSpan.FromSeconds(1));

syncPolicy.Execute(() =>
{
    // Your code goes here
});

// Create and use the IAsyncPolicy
IAsyncPolicy asyncPolicy = Policy
    .Handle<Exception>()
    .WaitAndRetryAsync(3, _ => TimeSpan.FromSeconds(1));
    
await asyncPolicy.ExecuteAsync(async token =>
{
    // Your code goes here
}, cancellationToken);

// Create and use the ISyncPolicy<T>
ISyncPolicy<HttpResponseMessage> syncPolicyT = Policy<HttpResponseMessage>
    .HandleResult(result => !result.IsSuccessStatusCode)
    .WaitAndRetry(3, _ => TimeSpan.FromSeconds(1));

syncPolicyT.Execute(() =>
{
    // Your code goes here
    return GetResponse();
});

// Create and use the IAsyncPolicy<T>
IAsyncPolicy<HttpResponseMessage> asyncPolicyT = Policy<HttpResponseMessage>
    .HandleResult(result => !result.IsSuccessStatusCode)
    .WaitAndRetryAsync(3, _ => TimeSpan.FromSeconds(1));

await asyncPolicyT.ExecuteAsync(async token =>
{
    // Your code goes here
    return await GetResponseAsync(token);
}, cancellationToken);
Things to remember:
  • Use ResiliencePipelineBuilder{<TResult>} to build a resiliency pipeline
  • Use one of the Add* builder methods to add a new strategy to the pipeline
  • Use either Execute or ExecuteAsync depending on the execution context

Migrating Policy Wrap

In v8, there’s no need to use policy wrap explicitly. Instead, policy wrapping is integrated into ResiliencePipelineBuilder.
IAsyncPolicy retryPolicy = Policy.Handle<Exception>().WaitAndRetryAsync(3, _ => TimeSpan.FromSeconds(1));

IAsyncPolicy timeoutPolicy = Policy.TimeoutAsync(TimeSpan.FromSeconds(3));

// Wrap the policies. The policies are executed in the following order:
// 1. Retry <== outer
// 2. Timeout <== inner
IAsyncPolicy wrappedPolicy = Policy.WrapAsync(retryPolicy, timeoutPolicy);

Migrating Retry Policies

Basic Retry

// Retry once
Policy
    .Handle<SomeExceptionType>()
    .Retry();

// Retry multiple times
Policy
    .Handle<SomeExceptionType>()
    .Retry(3);

// Retry multiple times with callback
Policy
    .Handle<SomeExceptionType>()
    .Retry(3, onRetry: (exception, retryCount) =>
    {
        // Add logic to be executed before each retry, such as logging
    });

// Retry forever
Policy
    .Handle<SomeExceptionType>()
    .RetryForever();

Wait and Retry

// Wait and retry multiple times
Policy
    .Handle<SomeExceptionType>()
    .WaitAndRetry(3, _ => TimeSpan.FromSeconds(1));

// Wait and retry forever
Policy
    .Handle<SomeExceptionType>()
    .WaitAndRetryForever(_ => TimeSpan.FromSeconds(1));

Retry with Result Handling

// Wait and retry with result handling
Policy
    .Handle<SomeExceptionType>()
    .OrResult<HttpResponseMessage>(result => result.StatusCode == HttpStatusCode.InternalServerError)
    .WaitAndRetry(3, _ => TimeSpan.FromSeconds(1));

Migrating Rate Limit Policies

The rate limit policy is now replaced by the rate limiter strategy which uses the System.Threading.RateLimiting package. Polly does not implement its own rate limiter anymore.
In v8, you have to add the Polly.RateLimiting package to your application otherwise you won’t see the AddRateLimiter extension.
// Create sync rate limiter
ISyncPolicy syncPolicy = Policy.RateLimit(
    numberOfExecutions: 100,
    perTimeSpan: TimeSpan.FromMinutes(1));

// Create async rate limiter
IAsyncPolicy asyncPolicy = Policy.RateLimitAsync(
    numberOfExecutions: 100,
    perTimeSpan: TimeSpan.FromMinutes(1));

// Create generic sync rate limiter
ISyncPolicy<HttpResponseMessage> syncPolicyT = Policy.RateLimit<HttpResponseMessage>(
    numberOfExecutions: 100,
    perTimeSpan: TimeSpan.FromMinutes(1));

// Create generic async rate limiter
IAsyncPolicy<HttpResponseMessage> asyncPolicyT = Policy.RateLimitAsync<HttpResponseMessage>(
    numberOfExecutions: 100,
    perTimeSpan: TimeSpan.FromMinutes(1));

Migrating Bulkhead Policies

The bulkhead policy is now replaced by the rate limiter strategy using ConcurrencyLimiter.
In v7, the bulkhead was presented as an individual strategy. In v8, it’s not separately exposed because it’s essentially a specialized type of rate limiter: the ConcurrencyLimiter.
In v8, you have to add the Polly.RateLimiting package to your application otherwise you won’t see the AddConcurrencyLimiter extension.
// Create sync bulkhead
ISyncPolicy syncPolicy = Policy.Bulkhead(
    maxParallelization: 100,
    maxQueuingActions: 50);

// Create async bulkhead
IAsyncPolicy asyncPolicy = Policy.BulkheadAsync(
    maxParallelization: 100,
    maxQueuingActions: 50);

// Create generic sync bulkhead
ISyncPolicy<HttpResponseMessage> syncPolicyT = Policy.Bulkhead<HttpResponseMessage>(
    maxParallelization: 100,
    maxQueuingActions: 50);

// Create generic async bulkhead
IAsyncPolicy<HttpResponseMessage> asyncPolicyT = Policy.BulkheadAsync<HttpResponseMessage>(
    maxParallelization: 100,
    maxQueuingActions: 50);

Migrating Timeout Policies

In v8, the timeout resilience strategy does not support pessimistic timeouts because they can cause thread-pool starvation and non-cancellable background tasks.
// Create sync timeout
ISyncPolicy syncPolicy = Policy.Timeout(TimeSpan.FromSeconds(10));

// Create async timeout
IAsyncPolicy asyncPolicy = Policy.TimeoutAsync(TimeSpan.FromSeconds(10));

// Create generic sync timeout
ISyncPolicy<HttpResponseMessage> syncPolicyT = Policy.Timeout<HttpResponseMessage>(TimeSpan.FromSeconds(10));

// Create generic async timeout
IAsyncPolicy<HttpResponseMessage> asyncPolicyT = Policy.TimeoutAsync<HttpResponseMessage>(TimeSpan.FromSeconds(10));

Migrating Circuit Breaker Policies

Polly V8 does not support the standard (“classic”) circuit breaker with consecutive failure counting.In V8 you can define a Circuit Breaker strategy which works like the advanced circuit breaker in V7.
// Create sync circuit breaker
ISyncPolicy syncPolicy = Policy
    .Handle<SomeExceptionType>()
    .CircuitBreaker(
        exceptionsAllowedBeforeBreaking: 2,
        durationOfBreak: TimeSpan.FromSeconds(1));

// Create async circuit breaker
IAsyncPolicy asyncPolicy = Policy
    .Handle<SomeExceptionType>()
    .CircuitBreakerAsync(
        exceptionsAllowedBeforeBreaking: 2,
        durationOfBreak: TimeSpan.FromSeconds(1));

Migrating Context

The successor of the Polly.Context is the ResilienceContext. The major differences:
  • ResilienceContext is pooled for enhanced performance and cannot be directly created. Instead, use the ResilienceContextPool class to get an instance.
  • Context allowed directly custom data attachment, whereas ResilienceContext employs the ResilienceContext.Properties for the same purpose.
  • In order to set or get a custom data you need to utilize the generic ResiliencePropertyKey structure.
// Create context
Context context = new Context();

// Create context with operation key
context = new Context("my-operation-key");

// Attach custom properties
context[Key1] = "value-1";
context[Key2] = 100;

// Retrieve custom properties
string value1 = (string)context[Key1];
int value2 = (int)context[Key2];

// Bulk attach
context = new Context("my-operation-key", new Dictionary<string, object>
{
    { Key1 , "value-1" },
    { Key2 , 100 }
});
Things to remember:
  • Use ResilienceContextPool.Shared to get a context and return it back to the pool
  • Use the ResiliencePropertyKey<TValue> to define type-safe keys for your custom data

Migrating Policy Registries

In v7, the following registry APIs are exposed:
  • IConcurrentPolicyRegistry<TKey>
  • IPolicyRegistry<TKey>
  • IReadOnlyPolicyRegistry<TKey>
  • PolicyRegistry<TKey>
In v8, these have been replaced by:
  • ResiliencePipelineRegistry<TKey>: Allows adding and accessing resilience pipelines
  • ResiliencePipelineProvider<TKey>: Read-only access to resilience pipelines
The main updates:
  • It’s append-only, which means removal of items is not supported to avoid race conditions
  • It’s thread-safe and supports features like dynamic reloading and resource disposal
  • It allows dynamic creation and caching of resilience pipelines using pre-registered delegates
  • Type safety is enhanced, eliminating the need for casting between policy types
// Create a registry
var registry = new PolicyRegistry();

// Add a policy
registry.Add(PolicyKey, Policy.Timeout(TimeSpan.FromSeconds(10)));

// Try get a policy
registry.TryGet<IAsyncPolicy>(PolicyKey, out IAsyncPolicy? policy);

// Try get a generic policy
registry.TryGet<IAsyncPolicy<string>>(PolicyKey, out IAsyncPolicy<string>? genericPolicy);

// Update a policy
registry.AddOrUpdate(
    PolicyKey,
    Policy.Timeout(TimeSpan.FromSeconds(10)),
    (key, previous) => Policy.Timeout(TimeSpan.FromSeconds(10)));
Polly V8 does not provide an explicit API to directly update a strategy in the registry. On the other hand it does provide a mechanism to reload pipelines dynamically.

Migrating No-Op Policies

V7V8
Policy.NoOpResiliencePipeline.Empty
Policy.NoOpAsyncResiliencePipeline.Empty
Policy.NoOp<TResult>ResiliencePipeline<TResult>.Empty
Policy.NoOpAsync<TResult>ResiliencePipeline<TResult>.Empty

Interoperability Between Policies and Resilience Pipelines

In certain scenarios, you might not be able to migrate all your code to the v8 API. For interoperability, you can define V8 strategies and use them with your v7 policies.
In v8, you have to add the Polly.RateLimiting package to your application otherwise you won’t see the AddRateLimiter extension.
// First, create a resilience pipeline
ResiliencePipeline pipeline = new ResiliencePipelineBuilder()
    .AddRateLimiter(new FixedWindowRateLimiter(new FixedWindowRateLimiterOptions
    {
        Window = TimeSpan.FromSeconds(10),
        PermitLimit = 100
    }))
    .Build();

// Now, convert it to a v7 policy
// Note that it can be converted to both sync and async policies
ISyncPolicy syncPolicy = pipeline.AsSyncPolicy();
IAsyncPolicy asyncPolicy = pipeline.AsAsyncPolicy();

// Finally, use it in a policy wrap
ISyncPolicy wrappedPolicy = Policy.Wrap(
    syncPolicy,
    Policy.Handle<SomeExceptionType>().Retry(3));

Summary

Migrating from Polly v7 to v8 involves:
  1. Understanding the shift from policies to strategies
  2. Using resilience pipelines instead of individual policies or policy wraps
  3. Adopting options-based configuration for all strategies
  4. Leveraging unified sync/async execution with a single pipeline instance
  5. Working with the new context pooling mechanism
  6. Using the new registry system for managing pipelines
For more information on specific strategies, see the individual strategy documentation pages.

Build docs developers (and LLMs) love