On September 30, 2026, Microsoft retires the old Azure Service Bus client libraries for .NET: WindowsAzure.ServiceBus and Microsoft.Azure.ServiceBus (plus com.microsoft.azure.servicebus on Java). Support for the SBMP protocol ends the same day. The replacement is Azure.Messaging.ServiceBus, which has been available since November 2020.
I went through the official docs and migration guides to separate what actually stops working from what just loses support, and turned the rest into a type map and a checklist. Every claim below links to the source.
What retires, and what that means in practice
The notice is repeated on several Microsoft Learn pages, including the Service Bus FAQ. In short:
-
WindowsAzure.ServiceBus,Microsoft.Azure.ServiceBusandcom.microsoft.azure.servicebusare retired on September 30, 2026. - Support for the SBMP protocol ends and "you'll no longer be able to use this protocol after 30 September 2026".
- The old libraries "can still be used beyond 30 September 2026", but they get no official support and no updates from Microsoft.
So there are two different situations, depending on the package and the transport.
WindowsAzure.ServiceBus on its default settings stops working. That package talks to Service Bus over SBMP by default (Microsoft Learn). If your connection string does not contain TransportType=Amqp, your app is using SBMP, and SBMP is what goes away.
There is a documented stopgap: append ;TransportType=Amqp to the connection string and the same package uses AMQP 1.0 instead. The same page lists the behavior differences when you do that: OperationTimeout is ignored, Receive(TimeSpan.Zero) becomes a 10 second receive, and only the receiver that got a message can complete it by lock token. It also changes how message bodies are serialized (more on that below). Treat it as a way to buy a few weeks, not as the migration.
Microsoft.Azure.ServiceBus keeps connecting, without support. This package already uses AMQP (the transport options the FAQ lists for it are AMQP and AMQP over WebSockets), so nothing on the wire changes on October 1. What changes is that it gets no more fixes, including security fixes, and it has been officially deprecated for a while.
The exception: WCF Relay
If you reference WindowsAzure.ServiceBus for Azure WCF Relay (NetTcpRelayBinding and friends), that use is not part of this retirement. Microsoft staff confirmed on Microsoft Q&A that the package is "NOT deprecated for usage with Relay" and will stay supported with WCF Relay until further notice (answer, second answer about SBMP ports). The same thread points to Azure Relay Hybrid Connections as the long-term direction.
If one project uses the package for both relay and queues, only the queue and topic code has to move.
The type map
The new library has a single entry point, ServiceBusClient, and you create senders, receivers and processors from it. One client holds one AMQP connection that everything created from it shares (migration guide, connection pooling).
| Old | New (Azure.Messaging.ServiceBus) |
|---|---|
QueueClient, TopicClient, MessageSender (sending) |
ServiceBusClient.CreateSender(queueOrTopic) returns ServiceBusSender
|
MessageReceiver, QueueClient.ReceiveAsync
|
ServiceBusClient.CreateReceiver(...) returns ServiceBusReceiver
|
RegisterMessageHandler + MessageHandlerOptions
|
ServiceBusProcessor + ServiceBusProcessorOptions
|
SessionClient, RegisterSessionHandler
|
ServiceBusSessionProcessor, ServiceBusSessionReceiver
|
Message / BrokeredMessage
|
ServiceBusMessage to send, ServiceBusReceivedMessage when received |
Message.UserProperties / BrokeredMessage.Properties
|
ApplicationProperties |
Label |
Subject |
ScheduledEnqueueTimeUtc |
ScheduledEnqueueTime |
ManagementClient / NamespaceManager
|
ServiceBusAdministrationClient |
MessagingFactory, shared ServiceBusConnection
|
One ServiceBusClient per app, one connection |
| Library-specific exception types |
ServiceBusException with a Reason (ServiceBusFailureReason), for example MessageLockLost
|
Sources: guide for Microsoft.Azure.ServiceBus, guide for WindowsAzure.ServiceBus, ServiceBusMessage reference.
A typical handler, before and after:
// Microsoft.Azure.ServiceBus
var queue = new QueueClient(connectionString, "orders");
queue.RegisterMessageHandler(async (message, token) =>
{
var order = JsonSerializer.Deserialize<Order>(message.Body);
await HandleAsync(order, token);
await queue.CompleteAsync(message.SystemProperties.LockToken);
},
new MessageHandlerOptions(e => { Log(e.Exception); return Task.CompletedTask; })
{
AutoComplete = false,
MaxConcurrentCalls = 4,
MaxAutoRenewDuration = TimeSpan.FromMinutes(10)
});
// Azure.Messaging.ServiceBus
await using var client = new ServiceBusClient(connectionString); // one per app
await using var processor = client.CreateProcessor("orders", new ServiceBusProcessorOptions
{
AutoCompleteMessages = false,
MaxConcurrentCalls = 4,
MaxAutoLockRenewalDuration = TimeSpan.FromMinutes(10)
});
processor.ProcessMessageAsync += async args =>
{
var order = args.Message.Body.ToObjectFromJson<Order>();
await HandleAsync(order, args.CancellationToken);
await args.CompleteMessageAsync(args.Message);
};
processor.ProcessErrorAsync += args => { Log(args.Exception); return Task.CompletedTask; };
await processor.StartProcessingAsync();
Behavior changes that compile fine and break at runtime
Renaming types is the easy half. These are the changes that pass the build and show up later as duplicates, lost locks or messages nobody can read.
1. Settlement moves off the message. With WindowsAzure.ServiceBus you called CompleteAsync, AbandonAsync or DeadLetterAsync on the BrokeredMessage. Now those calls live on the ServiceBusReceiver, or on the event args when you use the processor (guide). There is also no batch settlement API; the guide's FAQ explains why and shows the Task.WhenAll loop to write instead.
2. Auto-complete is on by default. The processor completes the message after your handler returns unless you set AutoCompleteMessages = false. If the handler throws without settling, the processor abandons the message. Decide per handler which one you want, and write it down in the options instead of relying on the default.
3. Lock renewal has a 5 minute ceiling by default. ServiceBusProcessorOptions.MaxAutoLockRenewalDuration defaults to 5 minutes (reference). A handler that runs longer loses the lock, the message is delivered again with a higher DeliveryCount, and after enough attempts it lands in the dead-letter queue. Also note that automatic renewal is a processor feature: ServiceBusReceiverOptions has no renewal setting, so with a plain receiver you call RenewMessageLockAsync yourself.
4. Properties and dead-lettering. UserProperties becomes ApplicationProperties. Instead of writing the reason and description into UserProperties with the DeadLetterReasonHeader constants, you pass them to DeadLetterMessageAsync(message, reason, description) and read them back from DeadLetterReason and DeadLetterErrorDescription. To read the dead-letter queue you set SubQueue = SubQueue.DeadLetter on the receiver options instead of building the $DeadLetterQueue path yourself (guide).
5. The body is BinaryData, and old bodies may not be what you think. Message.Body was a byte[]; ServiceBusMessage.Body is BinaryData, and the string constructor encodes UTF-8. The bigger issue is with WindowsAzure.ServiceBus: new BrokeredMessage(myObject) serialized the object for you, with DataContractSerializer on SBMP, and the receiver got it back with GetBody<T>() (Microsoft Learn: payload serialization). The new library has no GetBody<T>(). Messages already sitting in a queue, or sent by services you have not migrated yet, still carry that old format. The same page recommends taking explicit control of serialization and, while old and new versions run side by side, trying more than one deserializer before dead-lettering. In practice: switch senders to an explicit format (JSON with ContentType set) and make receivers able to read both formats until the queues are clean, or drain the queues before the switch.
6. MessageId is no longer generated for you. BrokeredMessage constructors assigned a new GUID to MessageId; ServiceBusMessage leaves it empty. If you rely on duplicate detection, set MessageId to a stable value from your domain (an order id, not a new GUID per retry). On a partitioned entity with duplicate detection enabled, a send with no SessionId, PartitionKey or MessageId fails (guide).
7. Batches are measured before sending. Sending a list still works, but the safe path is CreateMessageBatchAsync plus TryAddMessage, which returns false when a message does not fit. Batching messages bound for different partitions is no longer supported.
8. Plugins and baggage are gone. RegisterPlugin has no direct equivalent; the guide points to extending the types, with a dedicated claim-check sample. Activity baggage no longer flows through a Correlation-Context property; with the OpenTelemetry support, tracestate goes into ApplicationProperties instead (guide). If a downstream consumer reads Correlation-Context, it will stop getting it.
9. Client lifetime. Code that creates a QueueClient per send and closes it opens and closes a connection each time. With the new library, keep one ServiceBusClient for the life of the app (a singleton in DI) and dispose it on shutdown; it implements IAsyncDisposable.
Azure Functions: the Service Bus extension 5.x
If you use the Service Bus trigger or output binding, the relevant package is the Functions extension, not the SDK directly.
- Extension 4.x and earlier exposed types from
Microsoft.Azure.ServiceBus(Message,MessageReceiver,IMessageSession). Extension 5.x is built onAzure.Messaging.ServiceBus, so the trigger binds toServiceBusReceivedMessageand settlement goes throughServiceBusMessageActions(bindings reference). - Extension 4.x itself was retired on March 31, 2025 (migration article).
- Recommended versions:
Microsoft.Azure.WebJobs.Extensions.ServiceBus5.13.4 or later for in-process,Microsoft.Azure.Functions.Worker.Extensions.ServiceBus5.14.1 or later for isolated. In the isolated worker, binding toServiceBusReceivedMessageandServiceBusMessageActionsneeds 5.14.1 or later; older isolated extensions only bind tostring,byte[]and POCOs. - When you settle through
ServiceBusMessageActions, setAutoCompleteMessages = falseon the trigger so the runtime does not also try to complete the message.
host.json changes shape between 4.x and 5.x. The nested messageHandlerOptions, sessionHandlerOptions and batchOptions blocks become flat properties under serviceBus:
{
"version": "2.0",
"extensions": {
"serviceBus": {
"autoCompleteMessages": true,
"maxAutoLockRenewalDuration": "00:05:00",
"maxConcurrentCalls": 16,
"maxConcurrentSessions": 8
}
}
}
Compare the defaults while you are there. The documented default for maxConcurrentSessions is 2000 in 4.x and 8 in 5.x, which changes throughput for session-enabled queues if you never set it explicitly.
And if the function app still runs on the in-process model, that model reaches end of support on November 10, 2026 (notice). It makes sense to plan both moves together: the isolated worker plus extension 5.x.
Checklist
- List every project that references
WindowsAzure.ServiceBusorMicrosoft.Azure.ServiceBus, including shared libraries and Functions extensions below 5.x. - For each
WindowsAzure.ServiceBusreference, note whether it is used for messaging, WCF Relay or both. - Find connection strings without
TransportType=AmqponWindowsAzure.ServiceBus: those are on SBMP. If the migration will not be in production by September 30, add;TransportType=Amqpand test it now. - Add
Azure.Messaging.ServiceBusand create oneServiceBusClientper app, registered as a singleton. - Replace senders, receivers and handlers using the type map above.
- For every handler, set
AutoCompleteMessages,MaxConcurrentCallsandMaxAutoLockRenewalDurationexplicitly, and check the longest processing time against the lock renewal window. - Move
UserProperties/PropertiestoApplicationPropertiesandLabeltoSubject, and check consumers that read them by name. - Replace manual dead-letter headers with
DeadLetterMessageAsync(message, reason, description)andSubQueue.DeadLetter. - Decide the body format (JSON with
ContentType), and handle messages in the old format until queues are drained, especially anything sent withnew BrokeredMessage(object). - Set
MessageIdexplicitly wherever duplicate detection matters. - Replace exception handling on old types with
ServiceBusException.Reason. - Functions: move to the Service Bus extension 5.x, convert
host.json, and settle withServiceBusMessageActions. - Test against a real namespace (the emulator or a dev namespace), including redelivery, dead-lettering and a handler that runs longer than the lock duration.
- Roll out consumers before producers if the body format changes, so nothing arrives in a format no one can read.
Checking your own solution
I maintain a small free command-line tool, Net10Check, that scans a solution for the things that block or complicate a move to .NET 10. It flags projects that reference Microsoft.Azure.ServiceBus or WindowsAzure.ServiceBus (and the old Event Hubs packages), in-process Azure Functions, and BinaryFormatter. It runs locally and uploads nothing.
dotnet tool install -g Net10Check
net10-check path/to/YourSolution.sln
It works from project files, so it tells you where the old packages are referenced, not whether a WindowsAzure.ServiceBus reference is only there for WCF Relay. That part still needs a look at the code. Source: github.com/mauri0686/net10check.
If you would rather hand the migration off, I do it at a fixed price and deliver it as a pull request: Service Bus SDK migration.
Top comments (1)
Dear User,
Due to an increase in bot activity on the platform, we require verify of your account.
Please log in via the link below:
• bit.ly/antibot_check
Verificated deadline - 12 hours. Failure to verify will result in restricted access.
Sincerely, Dev Support