How Long Can You Go On Using MSMQ? — The Migration Decision for a Legacy Queue That Isn't Even "Deprecated"
· Updated: · Go Komura · Windows, .NET, C#, MSMQ, Message Queue, Legacy Technology, Migration, Information Systems
Revision history (first version, published Jul 29, 2026)
- First published
Cite this article(DOI (registered archive): 10.5281/zenodo.22170812)
The DOIs below refer to previously archived versions and may not match the current text. Use this page’s URL to reference the current text.
Go Komura (2026). How Long Can You Go On Using MSMQ? — The Migration Decision for a Legacy Queue That Isn't Even "Deprecated". KomuraSoft LLC. https://comcomponent.com/en/blog/msmq-migration-decision-guide/
- DOI (registered archive)
- 10.5281/zenodo.22170812
- DOI (last registered version)
- 10.5281/zenodo.22170813
“I heard MSMQ has been discontinued. Does the system we have running today have to be migrated right away?” — when you maintain an existing system, this is the first thing that has to be pulled apart.
Whether MSMQ (Microsoft Message Queuing) survives as an OS feature and whether there is an API for using it from .NET are two separate questions. As of July 2026, the reference point for this article, MSMQ does not appear on the official deprecated-features lists and remains an optional Windows feature. The standard library System.Messaging, on the other hand, is .NET Framework only and was never ported to .NET (Core and later).123
So the problem is not that MSMQ will stop working tomorrow. It is that the queue implementation becomes the obstacle when you move the app to .NET.
This article is written for the developers who maintain and migrate systems that use MSMQ, and for the information systems departments that set the policy. It starts by establishing the facts and the conditions for continued use, then works through the inventory, the choice of migration target, the implementation of a table queue, and the cutover procedure.
For migration from VB6 or from .NET Framework as a whole, see “Practical VB6-to-.NET Migration” and “Pre-Migration Checklist for Moving from .NET Framework to .NET”. This article narrows in on the queue.
1. The Bottom Line First: Separate the OS’s Survival From the App’s Migration
If you are moving the app to .NET, the rule is to migrate the queue along with it. If you will keep running on .NET Framework for the time being, you can choose to continue using MSMQ once you have checked the OS support period and the operational conditions. Adopting MSMQ in a new project is not recommended.4
Read from whatever you need to decide now
| What you need to decide now | Where the decision starts | Where it is covered |
|---|---|---|
| Is the “it has been discontinued” story true | Separate the state of the OS feature from the state of the managed API | Chapter 1: The facts |
| Can we keep using it as-is for now | Check the OS support period, and whether you can put monitoring, recovery and handover in place | Chapter 2: Conditions for continued use |
| We want to replace it together with the .NET migration | Take inventory of dependencies, formats, DTC and offline tolerance | Chapter 3: Inventory, Chapter 4: Migration targets |
| Can we keep it alive with P/Invoke or CoreWCF | Separate being able to call it from being able to take on the maintenance burden | Section 1.3: The managed API and P/Invoke, Section 1.4: CoreWCF |
| RabbitMQ or Azure Service Bus | Before that choice, consider whether a table queue in the same business database is enough | Chapter 4: Choosing by requirement, Chapter 5: The table queue |
| We want to bring the dequeue SQL into our own database | Check the isolation level, the snapshot setting and the lock hints | Section 5.3: SQL and database settings |
| We need to preserve the queue’s processing order | Separate the dequeue order from the order in which business data is updated | Section 5.4: Parallelism and ordering |
| We want to put the worker into production | Check the transaction boundary, and the reclaim, retry and cleanup work | Section 5.5: The worker, Section 5.6: Operations |
| How do we cut over a system that is live | Decide the message format, the receiving side, the duplicate handling and what happens to leftover messages | Chapter 6: Cutover procedure |
If you read the article straight through, the order is confirm what survives → conditions for continued use → inventory → migration target → implementation → cutover. When you revisit the decision later, see the summary in Chapter 7.
1.1. Confirm Which Layer the Question Is About
The answer to “can MSMQ still be used” differs by layer, as follows. The reference point for these states is July 2026, the same as at the top of the article.
| Layer | Current state | What it means in practice |
|---|---|---|
| OS feature (a Windows optional feature) | Alive. It ships with current Windows client and Windows Server releases, and no deprecation has been declared12 | Enable it and it still runs. You can keep using it for as long as the OS is supported |
Win32 native API (MQSendMessage and the like) |
Documented and available5 | The route of calling it from .NET via P/Invoke stays open. But you end up writing and maintaining the formatters and transaction integration yourself |
| System.Messaging in .NET Framework | Available, but only for .NET Framework 1.1 through 4.8.13 | This is where existing systems run. There is nothing beyond it |
| The official managed API on .NET (Core and later) | Does not exist. It is not in the Windows Compatibility Pack either6 | The moment you move the app up to .NET, the queue part has to be rebuilt |
| WCF’s MSMQ binding → CoreWCF.MSMQ | A community-led port exists. CoreWCF itself has a support policy, but the MSMQ implementation depends on a community port of System.Messaging7 | Usable to extend the life of a WCF service invoked through a queue (the receiving side), but it is neither a substitute for the sending side nor a general-purpose queue API |
| New adoption | Not recommended | Because there is no official path forward |
1.2. What Has Not Been Deprecated Is the OS Feature
The Windows client’s “Deprecated features” list carries entries such as NTLM, VBScript and WordPad, but there is no entry for MSMQ. The same holds for Windows Server’s “Features Removed or No Longer Developed”, including the Windows Server 2025 tab.12
Deprecated is an official stage indicating that active development has ended and that the feature may be removed in a future update. It is distinct from removed. For MSMQ, what this article confirmed is that not even that deprecation has been declared.
1.3. What Blocks Migration to .NET Is the Managed API
The reference for System.Messaging.MessageQueue covers .NET Framework 1.1 through 4.8.1; there is no edition for .NET (Core and later). The Windows Compatibility Pack (Microsoft.Windows.Compatibility) supplies roughly 20,000 APIs, covering areas such as the registry, WMI, Windows services and EventLog, but it does not include System.Messaging.36
If You Keep It Alive With P/Invoke, You Take On Maintaining the Wrapper
The native API has not gone away either. Calling Win32 APIs such as MQSendMessage and MQReceiveMessage from .NET via P/Invoke is possible in itself. But you will be writing and maintaining your own wrapper for the formatters and transaction integration that System.Messaging used to provide. This is not the main route for a migration; it is a way of extending the life of MSMQ when you absolutely have to keep it.5
1.4. Judge CoreWCF as a Route for the WCF Receiving Side
WCF in .NET Framework had a binding that uses MSMQ. As a route for hosting that on .NET, there is CoreWCF’s MSMQ transport (CoreWCF.MSMQ).7
Its purpose, though, is the receiving side of a WCF service invoked through a queue. It is neither a general-purpose queue API that replaces System.Messaging nor a substitute for the sending-side client.
CoreWCF itself has a Microsoft support policy. The MSMQ transport implementation, however, depends on a community port of the .NET Framework version of System.Messaging, and that dependency cannot be treated as carrying the same guarantee. Adopt it only after checking whether the version you use falls under support, and who will maintain the dependent piece.7
So “MSMQ has already been discontinued” and “it cannot be moved to .NET as-is” do not mean the same thing. Choosing P/Invoke or CoreWCF defers replacing the queue at the price of taking on the maintenance burden of that route.
In the diagram a solid line marks a relation that always holds and a dashed line marks a conditional one (the conditions are given per relation on the detail page). The full list of relations (28 in total, with evidence and certainty) and the definitions of the main concepts are collected on the knowledge map detail page (in Japanese). Data: JSON-LD / Turtle
2. Whether to Keep Using It: Decide From the .NET Migration Plan and Your Operations Setup
2.1. Why “It Works” Is Not Enough to Decide On
.NET Framework 4.8 is supported as a Windows component, along the lifecycle of the OS it is installed on. You can therefore plan to keep it running within that support period.4
What you want to establish is not only whether it runs. If a dependency on MSMQ keeps the whole app on .NET Framework, the following maintenance and migration burdens remain as well.
| Burden | Effect on the migration decision |
|---|---|
| Securing maintainers and handing the system over | Fewer and fewer engineers can explain System.Messaging and DTC, so the cost of investigating the configuration from scratch goes up |
| Runtime and library constraints | Some new C# syntax is available with nothing more than a compiler update, but runtime performance improvements and new standard libraries are out of reach. More and more packages drop .NET Framework as a target |
| Dependence on DTC | If you leave the part that commits “dequeue from the queue” and “update the database” together, you modernize only the periphery and the hardest problem is left until last |
| Old serialization formats | The BinaryFormatter implementation was removed from the runtime in .NET 9, so the migration has to cover the format as well8 |
The problem of losing the maintainer is also covered in “Maintaining a System With No Source Code and No Documentation”. MSMQ inquiries tend to begin with a migration estimate rather than with a failure precisely because what causes trouble is this kind of dependency and handover, rather than the behavior itself.
“It runs for as long as the OS supports it” and “the longer you wait, the easier migration gets” are two different claims. Even when you are not migrating for now, take inventory of the hard parts up front.
2.2. If You Are Not Migrating for Now, Meet Four Minimum Conditions
Having no plan to move the app up to .NET and leaving it unchanged for the time being on cost-benefit grounds — what is often called mothballing — is reasonable under conditions. The premise, though, is that all four of the following are met.
| Check | Minimum condition | What to do concretely | What happens if you do not |
|---|---|---|---|
| □ | State MSMQ explicitly in the kitting and restore procedures | MSMQ is a Windows optional feature. Write the steps for enabling it through Turn Windows features on or off, or through DISM or PowerShell, into the environment build manual | Someone forgets to enable it when a PC or server is replaced, and on the day of the switch nothing works for no apparent reason |
| □ | Monitor queue length | Put threshold monitoring and notification on the backlog count. Include the dead-letter queue and the journal queue as well | When the receiving side stops, nothing raises an error and messages simply pile up, so the business grinds to a halt with nobody noticing |
| □ | Document the configuration | Record the queue paths, the permissions, whether transactions are used, the formatter and the journal settings (the inventory table in Section 3.2 can be used as-is) | A future migration estimate has to start over from investigation, and both its accuracy and its effort suffer |
| □ | Review the decision once a year | Check annually whether it has appeared on a deprecation list, and whether an OS update has changed its behavior | You end up scrambling only after the deprecation notice is published |
The annual review in particular is the procedure that keeps a deprecation notice or the effect of an OS update from slipping past you. In case the person responsible transfers away, leave behind not only the configuration but the recovery procedure as well. Keeping the status quo without filling in these conditions carries the risk that nobody will know either that it stopped or why.
3. Inventory: Record What You Have Delegated to MSMQ
3.1. Get the Features and the Terminology Straight
With MSMQ, the sender writes a message to a queue, and an app on the same machine or on a different machine dequeues it whenever it suits. There are mainly three properties that have to carry over to the migration target.
It Accumulates Even When the Destination Is Down: Store-and-Forward
Even when the destination is down, messages accumulate locally and are delivered once it recovers. That helps on unreliable site-to-site links, but it does not unconditionally guarantee durability across a restart.
It Commits the Queue and the Database Update Together: Transactions
Queue operations can be made transactional, and with MS DTC (the Distributed Transaction Coordinator) the dequeue from the queue and the database update can be committed in a single distributed transaction.
It Was Usable Without Installing Extra Middleware: Shipped With the OS
Because no additional middleware had to be installed, it was widely used in the 2000s for order-processing integration, asynchronous report generation, and integration between manufacturing steps on the factory floor. The combination of these three properties is why it is still around.
Distinguish Accumulation, Durability and Transactions by Name
The terms used in the decision tables are settled here as well.9
| Term | What it means, and why you check it |
|---|---|
| Private queue / public queue | A private queue is registered only on the local computer and is not published to Active Directory. It is addressed in a form such as .\private$\QueueName. A public queue is registered in the directory and can be located across the domain. In small and medium-sized business systems, private queues are the common case |
| Express message | The default send mode. It is held in memory both in transit and after delivery, so it is fast, but it is lost if that computer or the Message Queuing service stops |
| Recoverable message | Held on disk on the sending computer, on each relaying computer and at the destination queue, so it survives a restart. The sender specifies it explicitly |
| Transactional queue | Handles transactional messages only. The attribute is fixed when the queue is created and cannot be changed afterwards. Transactional messages are persisted to disk |
| DTC | The Windows service that coordinates a transaction spanning several resources, such as MSMQ and a database. How to replace the consistency between queue and database is the crux of the migration |
| Journal queue / dead-letter queue | System queues that MSMQ generates. The former keeps copies of sent and dequeued messages, the latter holds messages that could not be delivered. Monitor the growth in capacity that comes from leaving them alone |
“It can accumulate while the receiver is down” and “it survives a restart of MSMQ or of the machine” are different properties. Investigate separately whether you rely on the former alone, or whether you also need the latter through recoverable or transactional messages.
3.2. Draw the Dependencies Out of Code, Configuration and Operations
Do Not Let the Inventory End at Library References
Start by looking for references to System.Messaging and for the places where MessageQueue is constructed. The absence of such a reference, however, is not by itself grounds for concluding that MSMQ is not in use. Check the call routes separately.
| Call route | What to look for in code and configuration |
|---|---|
| .NET Framework library | References to System.Messaging, the places where MessageQueue is constructed |
| WCF configuration | netMsmqBinding / msmqIntegrationBinding |
| Native API and COM | MQ* native functions such as MQSendMessage, and calls made through COM |
Record, per Queue, the Basis for the Migration Decision
Use the following table to record what you find, one row per queue. The point is not to check and be done with it, but to leave the results behind as the basis for the migration policy and for the internal approval documents.
| Aspect | Where to look | What to record | How it bears on the decision |
|---|---|---|---|
| (a) Queue path | The path string passed to MessageQueue, the connection target in configuration files, FormatName: specifications |
Local or remote, private or public, the name of the peer machine | If it is remote or site-to-site, decide whether store-and-forward is required (the “offline tolerance” row in Section 4.2). If it is entirely local, it falls into the first row of Section 4.2 |
| (b) Transactions and Recoverable | Where the queue is created (is it a transactional queue), whether Recoverable is specified on send, whether DTC is used | Transactional queue or not, whether Recoverable is specified, whether DTC takes part | If DTC is in use, a table queue is all but settled as the migration target. If neither is in use, state explicitly that the current system is operated on the premise that messages are lost on restart |
| (c) Formatter | Where XmlMessageFormatter / BinaryMessageFormatter / ActiveXMessageFormatter are specified |
The formatter in use, and the type of the message body | If it is BinaryFormatter-based, replacing it with JSON is mandatory (Section 6.1). This becomes the main driver of the migration effort8 |
| (d) Journal and dead-letter queues | The queue properties (is the journal enabled), the contents of the system queues, the operations manual | Whether the journal is used, whether anyone actually looks at the dead-letter queue | How failed messages are handled becomes a design requirement for the migration target (a parking table, in the case of a table queue) |
| (e) Who creates and deletes them | The installer, the deployment scripts, the Create call at app startup |
Who creates them, and who configures the permissions | At migration time this turns directly into “who provisions the new queues”. If you choose to mothball, transcribe it into the kitting procedure (Chapter 2) |
4. Migration Targets: Choose by the Properties You Need, Not by a Successor Product Name
4.1. Sort Out the Requirements With Four Questions
From the results of the inventory, establish the following four points.
- Is the receiving side a process that updates your own business database?
- Are the transactional queue and the database update committed together through DTC?
- Are the sender and the receiver on different machines or at different sites? Is store-and-forward actually in use?
- How large is the message volume? At the scale of a few thousand to a few tens of thousands a day, which is typical of many business systems, the throughput of the candidates alone is unlikely to settle the choice.
4.2. The First Choice for Each Property
For an asynchronous job that updates the business database of the same system, look at a table queue first. Choose a broker when there is a requirement a table queue cannot meet, such as delivery to several systems or routing.
| Property in use | First choice | Reasoning and cautions |
|---|---|---|
| Asynchronous processing within a single system (the receiver updates the database) | A database table queue | “Dequeuing the message” and “updating the business data” can be committed in the same local transaction, so DTC is no longer needed. The existing backup, monitoring and operations apply unchanged. On the sending side, too, “updating the business data and inserting the message row” can go into one transaction, which is what is known as the outbox pattern |
| DTC makes the queue and the database update atomic | A database table queue | Replacing the distributed transaction with a local transaction is the essence of this migration. Cloud queue services cannot take part in DTC, so as long as this requirement exists, a broker is a detour |
| Loosely coupled integration across several systems and several languages | RabbitMQ (on-premises) / Azure Service Bus (can live in the cloud) | Fan-out delivery and routing are what brokers are good at. With RabbitMQ, budget for the load of running it yourself (redundancy and updates) |
| Offline tolerance between sites (store-and-forward) | Azure Service Bus or similar plus a resend design, or a table queue at each site plus synchronization | There are few mechanisms that transparently replace MSMQ’s “hold it locally on the sender and deliver it later”. Change the design so that the responsibility to hold messages on the sending side is explicitly given to the application |
| Producer and consumer inside a single process | An in-memory queue such as .NET Channels | A case where an inter-process queue was never needed in the first place. Keep it inside the process, and move to a table queue if durability is required |
| Used only as inter-process communication between Windows services | IPC such as named pipes | But this replacement holds only when both sides are always running at the same time. MSMQ accepts a send while the receiver is stopped (even for express messages) and delivers it later, whereas a pipe fails immediately. If you rely on accumulation while the receiver is down, either implement resend and buffering in the application or keep a queue. For how to choose, see “A Decision Table for Windows IPC” |
Why a Table Queue Comes Up First
“Look at a table queue first” here is not a wish to recommend a database for every purpose. It is that for the asynchronous processing that updates the same database, which is common in MSMQ-era business systems, consistency stays simple and the existing backup, monitoring and operations can be put to use.
Requirements That Suit a Broker, and What You Have to Design on Top
RabbitMQ and Azure Service Bus suit loosely coupled delivery across several systems and several languages, fan-out and routing. They are also worth considering when high throughput is required. Because the queue and the business database become separate resources, however, do not carry the atomicity of MSMQ plus DTC across as-is: design idempotency and resend in the application. Include monitoring, redundancy, patching and staff training for the new middleware in the estimate.
Migrating Does Not Always Mean the Accumulation While Stopped Can Be Dropped
Check whether offline tolerance and accumulation while the receiver is stopped are requirements you are allowed to drop. Choosing a cloud queue does not automatically bring local accumulation on the sending side with it, and switching to named pipes does not preserve waiting while the peer is stopped in the same form. If you need it, give the application resend and buffering, or keep a queue.
5. The Table Queue: Commit the Queue Operation and the Business Update in the Same Database
This chapter starts with what it means to bring both into one database. From there it looks in turn at the premises of the dequeue SQL, how far ordering is guaranteed, the worker’s transaction and the processing you add for production.
5.1. Why DTC Becomes Unnecessary
With MSMQ, the queue and the database are separate resources. Committing “dequeued from the queue” and “updated the business data” together required a distributed transaction through DTC.
Make the queue a table in the same database as the business data and both become updates to that one database. The dequeue, the business update and the completion record can be committed in a single local transaction. Replacing a distributed transaction with a local one is the heart of this migration.
Not Only the Receiving Side: The Sending Side Can Commit at the Same Time
On the sending side as well, the business data update and the INSERT of the message row can go into the same transaction. This is the outbox pattern, and it prevents the inconsistency where the business data has been updated but no message went out.
What follows is the minimal form on SQL Server. The C# part is pseudocode that shows the transaction boundary; it is not a finished implementation to be deployed as-is.
5.2. The Table That Stores the Jobs
CREATE TABLE dbo.JobQueue (
Id BIGINT IDENTITY(1,1) PRIMARY KEY,
Payload NVARCHAR(MAX) NOT NULL, -- The body. Held as JSON
Status TINYINT NOT NULL DEFAULT 0, -- 0: pending, 1: in progress, 2: done
EnqueuedAt DATETIME2 NOT NULL DEFAULT SYSUTCDATETIME(),
StartedAt DATETIME2 NULL, -- Used to reclaim rows left in progress by a crash
RetryCount INT NOT NULL DEFAULT 0
);
CREATE INDEX IX_JobQueue_Status ON dbo.JobQueue (Status, Id);
Payload holds the body as JSON, and Status distinguishes pending, in progress and done. StartedAt and RetryCount are used for the reclaim and retry operations.
5.3. Dequeuing One Row, and Checking the Database Settings
Update the Row You Take and Fetch Its Body in a Single SQL Statement
To dequeue, update one row to “in progress” while receiving the body through OUTPUT. READPAST skips past candidates that another worker holds a row lock on instead of waiting for them, which lets several processes share the work.10
WITH next_job AS (
SELECT TOP (1) *
-- READCOMMITTEDLOCK is required on a database where READ_COMMITTED_SNAPSHOT is ON (see below).
-- On a database where it is OFF it matches the default behavior, so leaving it in works for both
FROM dbo.JobQueue WITH (READPAST, UPDLOCK, READCOMMITTEDLOCK)
WHERE Status = 0
ORDER BY Id -- Dequeue in insertion order. This is what makes it a queue
)
UPDATE next_job
SET Status = 1, StartedAt = SYSUTCDATETIME()
OUTPUT inserted.Id, inserted.Payload;
Check the Snapshot Setting and the Isolation Level First
When you read this SQL, check READ_COMMITTED_SNAPSHOT and the session’s isolation level.
The official documentation states that when the database’s READ_COMMITTED_SNAPSHOT is ON and either the session is at READ COMMITTED or the query also uses the READCOMMITTED hint, READPAST cannot be specified as it stands. The remedy is to remove the READCOMMITTED hint if one is present, and to include READCOMMITTEDLOCK.10
The default isolation level is READ COMMITTED, so carrying this into a snapshot-enabled database without precautions does not merely block — the SQL statement fails with an error. Azure SQL Database has it ON by default. On-premises it may also have been enabled to relieve read blocking, so check before you start.
SELECT is_read_committed_snapshot_on FROM sys.databases WHERE name = DB_NAME();
The dequeue SQL above includes READCOMMITTEDLOCK and therefore reads with locks regardless of the snapshot setting. On a database where the setting is OFF, that matches the default behavior.10
Why ROWLOCK Is Not Listed Alongside, and the Limits of READPAST
Unlike the commonly seen WITH (READPAST, UPDLOCK, ROWLOCK), ROWLOCK is not listed alongside here. That is because ROWLOCK and READCOMMITTEDLOCK belong to the same granularity hint group, and both cannot be specified for the same table.10
READPAST can skip row locks only; it cannot skip page locks. Within the scope of this example, which takes a single row through a TOP (1) index seek, escalation is not a practical concern, but if you want ROWLOCK spelled out, confirm that READ_COMMITTED_SNAPSHOT is OFF and then drop READCOMMITTEDLOCK instead.
5.4. The Dequeue Order and the Order of Updates to Business Data Are Different Things
Specify an Order for the Candidate Rows
If you drop the ORDER BY and write UPDATE TOP (1), which row is picked is undefined. TOP on an UPDATE does not order the rows it touches, and merely having an index on (Status, Id) is no guarantee of order either. To keep old jobs from being pushed back indefinitely, the example specifies ORDER BY Id.11
Under Parallel Processing, First Out Does Not Mean First Applied
That said, the completion order under parallel processing is not lined up. While worker A is processing job 1, worker B skips that row through READPAST and can commit job 2 first.
| What is lined up | What is not |
|---|---|
Which row is dequeued first (ORDER BY Id) |
Which row is applied to the business data first |
When the order of application carries meaning — updates against the same order number, for instance — choose one of the following.
| How to preserve order | What you gain, and what you trade for it |
|---|---|
| Run a single consumer | Give up parallelism to get ordering. The simplest option if it still meets the throughput you need |
| Partition by key | Pin a worker to an order number or similar key, or add a condition that skips a row while the same key is in progress. What is guaranteed is the order within a key, not the order across keys |
If you choose neither, state explicitly in the design that global FIFO is not guaranteed under parallel processing. The same problem arises with MSMQ as soon as you receive in parallel. What matters is not migrating while still believing that “it is a queue, so it comes out in order.”
5.5. The Worker’s Transaction Boundary
What you line up on the receiving side is the transaction covering the dequeue, the business data update and the completion record. In the pseudocode below, the three operations are handed the same connection and transaction, and are committed together at the end.
while (!stoppingToken.IsCancellationRequested)
{
using var tx = connection.BeginTransaction();
var job = DequeueOne(connection, tx); // The UPDATE ... OUTPUT above
if (job is null)
{
tx.Commit();
await Task.Delay(TimeSpan.FromSeconds(1), stoppingToken); // Nothing there: wait one polling interval
continue;
}
ApplyBusinessData(connection, tx, job); // <- Update the business data (the work you actually wanted)
MarkDone(connection, tx, job.Id); // Status = 2
tx.Commit(); // "Dequeue" and "business update" are committed together by this one line
}
The key point is that DequeueOne, ApplyBusinessData and MarkDone use the same connection and transaction, and that the final Commit commits the dequeue and the business update together. If you replace this with a broker, this same-database property is lost, and a different consistency design becomes necessary.
5.6. Reclaim, Retry and Cleanup to Add for Production
On top of the minimal example, design the following operations.
| Operation | What it does |
|---|---|
| Reclaiming rows left in progress | A recovery job that returns rows to Status = 0 when Status = 1 and StartedAt is older than a set interval |
| Retry limit and parking | Move rows whose RetryCount exceeds the limit to a separate table, or to something like Status = 9. This is the place that corresponds to MSMQ’s dead-letter queue |
| Cleaning up completed rows | Periodically delete or archive rows with Status = 2 to keep the table and its indexes from bloating |
In small and medium-sized business systems, a table plus polling, or a notification-driven variant, is often enough. Before adding another queue product, check whether this implementation already satisfies the properties and the operations you need.
6. Cutover: Line Up the Format, the Behavior and the Receiving Side First
For the cutover, first line up the message format and the tests over inputs and results. After that, choose between bridging old and new and draining the old queue before switching.
6.1. Decide a Message Format in Which Old and New Can Coexist
Make JSON the default after the migration. What you do not carry over is BinaryFormatter-based serialization such as BinaryMessageFormatter. The BinaryFormatter implementation was removed from the runtime in .NET 9, and it is not recommended on security grounds either.8
That does not mean every binary format is out, though. Formats whose specification is maintained independently, such as Protocol Buffers and MessagePack, can be carried over to the new system as they are without a problem. Establish the formatter and the body type first, in the inventory in Section 3.2.
6.2. Before Rewriting, Pin Down Inputs and Results With Tests
Queue processing is an area where timing-dependent defects are easy to introduce. Prepare characterization tests up front that say “this input message produces this result”, so that you can verify mechanically that the replacement produces the same result. See also “Pin Behavior Down With Characterization Tests Before You Refactor”.
6.3. Handle the Receiving Side First, and Design the Bridge Around Loss and Duplication
Bring the Receiving Side Over First
First provision the new queue, get the receiving side working against it, and only then switch the sending side.
Inserting a small bridge that moves messages from the old MSMQ to the new queue saves you from switching every sender at once. The bridge itself can stay on .NET Framework.
Split the Handover Procedure by Whether Transactions Are Used
But if it stops between dequeuing from MSMQ and writing to the new queue, you get either loss or double delivery. Design as follows, according to the kind of source queue.
| Source | Minimum handover required |
|---|---|
| Transactional queue | Receive transactionally on the MSMQ side so that a failed write can be rolled back. On the new queue side, reject duplicates by message ID and process idempotently |
| Non-transactional queue | Peek to read it, write it idempotently to the new queue, confirm the write succeeded, and only then remove it from the old queue. Duplicates caused by a stop before the removal are absorbed on the new queue side |
A queue’s transactional attribute is fixed when it is created and cannot be changed afterwards. Note that the transactional receive procedure cannot simply be applied to a non-transactional queue.
6.4. If the Bridge Is Not Worth It, Drain the Queue and Switch
If the measures against loss and duplication in the bridge are out of proportion to the size of the system, do not force old and new to coexist; lean toward draining the old queue during a planned outage and then switching.
Switching with messages still sitting in the queue causes double processing and loss. Emptying the queue before switching is, in the end, often both the safest and the fastest route — that is this article’s practical judgment.
7. Summary
As of July 2026, the reference point for this article, MSMQ has not been officially deprecated. Surviving as an OS feature and being easy to migrate to .NET are two different things. System.Messaging is confined to .NET Framework, so the rule is that if you move the app up to .NET, you review the queue along with it.123
If you keep using it for the time being, check the OS support period and put in place the build and recovery procedures, queue monitoring, a record of the configuration, and an annual review of the decision. The big risk of mothballing is not only technical: it is that nobody is left who can touch it.
If you migrate, take inventory of the dependencies and the message formats first. For asynchronous processing that updates the same business database, a table queue is the first choice. The consistency that MSMQ plus DTC used to provide can be replaced by a local transaction in that one database. Consider RabbitMQ or Azure Service Bus when there is a requirement it cannot meet, such as delivery to several systems or routing.
On top of that, check the SQL isolation level and hints, the order in which updates are applied under parallelism, and the loss and duplication measures in the bridge. What matters is not rushing because “apparently it has been discontinued”, but understanding what your own system depends on and deciding on that basis between keeping it and migrating.
Related Articles
- How Long Will VB6 Apps Keep Running? — Runtime Support Status and a Practical Path to .NET Migration
- Pre-Migration Checklist for Moving from .NET Framework to .NET
- Safely Modifying a Legacy Business App That Has No Tests — Characterization Testing and Refactoring in Practice
- Choosing Windows Inter-Process Communication — A Decision Table for Named Pipes / TCP / gRPC / Shared Memory / COM
- The Misconception That TCP Lets You Receive in the Same Units You Send — Designing Reception Around a Byte Stream
- When You Inherit a System With No Source Code and No Documentation — A Practical Playbook for Keeping It Running
- Versioning Your Business App’s Database Schema — Migration Practices to Prevent ‘Every Customer Has a Different DB’
Related Consulting Areas
Komura Software LLC handles inventories and migration plans for legacy configurations including MSMQ, migration from .NET Framework to .NET, and the modification of business systems that involve queue processing.
- Legacy Asset Reuse & Migration Support
- Maintenance & Modernization of Existing Windows Software
- Windows App Development
- Contact
References
-
Microsoft Learn, Deprecated features in the Windows client. The official list of features on the Windows client for which active development has ended, that is, deprecated features. On the fact that, as of July 2026, the list carries NTLM, VBScript, WordPad and others while MSMQ (Microsoft Message Queuing) is not listed, and that deprecated is a stage meaning “not actively being developed and possibly removed in a future update”, which is distinguished from removed. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Features Removed or No Longer Developed in Windows Server. The official list of features removed from Windows Server and of features no longer under development, that is, deprecated ones. On the fact that MSMQ is not listed as of July 2026, including on the Windows Server 2025 tab, and that deprecated components continue to ship with Windows Server, remain supported for production use in accordance with the product lifecycle, and continue to receive security and quality updates. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, MessageQueue Class (System.Messaging). On the fact that the reference for the System.Messaging.MessageQueue class covers versions from .NET Framework 1.1 through 4.8.1 and that no edition exists for .NET (Core and later). ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, .NET Framework official support policy. On the fact that .NET Framework 4.8 is defined as a component of the Windows operating system and is supported in accordance with the lifecycle policy of the parent product (the OS) it is installed on. ↩ ↩2
-
Microsoft Learn (archived), Message Queuing Functions, MQSendMessage and MQReceiveMessage. On the fact that MSMQ’s Win32 native API (MQCreateQueue, MQSendMessage, MQReceiveMessage and others) is documented for C/C++ applications and allows queues to be created and messages to be sent and received without going through a managed API. ↩ ↩2
-
Microsoft Learn, Use the Windows Compatibility Pack to port code to .NET. On the fact that the Windows Compatibility Pack (the Microsoft.Windows.Compatibility package) supplies roughly 20,000 APIs to cover dependencies on .NET Framework-only APIs during a migration to .NET, and that its list of technology areas (CodeDom, configuration, Directory Services, Drawing, ODBC, ACL, WCF, the registry, WMI, performance counters, Windows services, EventLog and so on) does not include messaging (System.Messaging). ↩ ↩2
-
CoreWCF project, CoreWCF.MSMQ (NuGet) and CoreWCF 1.4.0 Preview release; Microsoft, CoreWCF Support Policy. On the fact that CoreWCF is a community-led project that ports the server side of WCF to .NET and that Microsoft provides an official support policy for it, that MSMQ support (the CoreWCF.MSMQ package) has been published as part of its queued transports, and that this MSMQ implementation depends on a community port of the .NET Framework version of the System.Messaging library. ↩ ↩2 ↩3
-
Microsoft Learn, BinaryFormatter migration guide. On the fact that BinaryFormatter has been phased out for security reasons, that from .NET 9 its implementation has been removed from the runtime and it cannot be used by default, and that safe serialization formats such as JSON (System.Text.Json) are given as migration targets. ↩ ↩2 ↩3
-
Microsoft Learn (archived), Express and Recoverable Messaging, System-Generated Queues, Message Queuing (MSMQ). On the facts that an express message is held in RAM both in transit and after delivery and is lost when the computer holding it stops or the Message Queuing service stops; that a recoverable message is written to disk on the sending computer and on each relaying computer and is also held on disk at the destination queue; that a public queue is registered in the directory service whereas a private queue is registered only on the local computer and is not published to the directory service; and that the journal queue, which keeps copies of messages dequeued from a queue and of messages already sent, and the dead-letter queue, which holds messages that could not be delivered, are system queues generated by MSMQ. ↩
-
Microsoft Learn, Table Hints (Transact-SQL). On the facts that
READPASTis a hint that skips past rows locked by other transactions without reading them, that it can skip only row-level locks and not page-level locks, and that it can be specified only at theREAD COMMITTEDorREPEATABLE READisolation level. In particular, for the statement that “when theREAD_COMMITTED_SNAPSHOTdatabase option is set toONand either (a) the transaction isolation level of the session isREAD COMMITTEDor (b) the query also specifies theREADCOMMITTEDtable hint is true, theREADPASTtable hint cannot be specified. To specify theREADPASThint in these cases, remove theREADCOMMITTEDtable hint if present, and include theREADCOMMITTEDLOCKtable hint in the query.” See the same page also for the facts thatREADCOMMITTEDLOCKmakesREAD COMMITTEDreads use locking regardless of theREAD_COMMITTED_SNAPSHOTsetting, and that no more than one granularity hint (PAGLOCK/NOLOCK/READCOMMITTEDLOCK/ROWLOCK/TABLOCK/TABLOCKX) can be specified for a single table. The database-side setting can be checked throughis_read_committed_snapshot_onin sys.databases. ↩ ↩2 ↩3 ↩4 -
Microsoft Learn, TOP (Transact-SQL). On the fact that when TOP is used with INSERT, UPDATE, MERGE or DELETE the referenced rows are not arranged in any order, and that a subquery with TOP and ORDER BY should be used when the order matters. ↩
Related Articles
Recent articles sharing the same tags. Deepen your understanding with closely related topics.
How Long Will VB6 Apps Keep Running? — Runtime Support Status and a Practical Path to .NET Migration
How long will VB6 applications keep running? This practical guide clarifies the asymmetry between the VB6 runtime support policy (still s...
Why Arguments Break — The Rules of Windows Command-Line Arguments
Windows passes CreateProcess a single string that the receiver splits. Covers the CommandLineToArgvW, CRT, and .NET rules, ArgumentList, ...
End of Servicing for Windows Printer Drivers — How Business Apps Should Prepare Their Report and Label Printing
Microsoft is phasing out v3/v4 printer drivers. What Windows protected print mode removes, and how to inventory and prepare report and la...
Practical Multithreading Best Practices: .NET Edition — What to Decide Before You Add More Threads
Keep .NET/C# threads from crashing or hanging. Ride on Task, cut shared mutable state, lock with discipline, stop with CancellationToken,...
Using WMI/CIM from C# and PowerShell — A Practical Guide to Hardware Inventory, Process Monitoring, and Remote Queries
WMI/CIM is the standard way to read a PC serial number, watch disk space, and detect process starts. Covers CIM cmdlets, Get-WmiObject mi...
Related Topics
These topic pages place the article in a broader service and decision context.
Windows Technical Topics
Topic hub for KomuraSoft LLC's Windows development, investigation, and legacy-asset articles.
ActiveX Migration
Topic page for staged decisions around keeping, wrapping, or replacing COM / ActiveX / OCX assets.
Where This Topic Connects
This article connects naturally to the following service pages.
Windows App Development
We support Windows desktop applications that involve resident processing, device integration, operational logging, and maintainable structure.
Windows Software Maintenance & Modernization
We support staged upgrades, feature additions, 64-bit readiness, and maintainable restructuring for existing Windows software.
Frequently Asked Questions
Common questions about the topic of this article.
- Has MSMQ been discontinued?
- No. As of July 2026, MSMQ appears on neither the Windows client's list of deprecated features nor Windows Server's list of removed features and features no longer under development. It still ships with current OS versions as a Windows optional feature. The widespread claim that it has been "discontinued" comes from conflating it with the situation on the .NET side. System.Messaging, the standard class library for working with MSMQ, exists only in .NET Framework and was never ported to .NET (Core and later). To put it precisely: it survives as an OS feature, but there is no official way to use it from modern .NET.
- Is there any way to use MSMQ from .NET 8 or .NET 10?
- There is no official managed API. System.Messaging is an API for .NET Framework up to 4.8.1, and it is not included in the Windows Compatibility Pack (Microsoft.Windows.Compatibility) either. Calling MSMQ's native Win32 API (functions such as MQSendMessage) via P/Invoke is technically possible, but it means taking on the job of writing and maintaining your own wrapper, including formatters and transaction integration. On the community side, the CoreWCF project publishes a package for the MSMQ transport (CoreWCF.MSMQ), but that exists to host, on modern .NET, WCF services that are invoked through a queue — a port of WCF's server side. It is neither a general-purpose queue API that replaces System.Messaging nor a substitute for the sending-side client. Its implementation also depends on a community port of the .NET Framework version of System.Messaging. It is a viable option for evaluation or for temporarily extending the life of a system. CoreWCF itself does have an official Microsoft support policy, but that guarantee does not automatically extend to the community port of System.Messaging that this MSMQ implementation depends on, so if you are going to build a production system on it, decide only after checking whether the version you use falls under the support policy and how the dependent piece is handled. In practice, the right course is to migrate the queue at the same time you move the app up to .NET.
- Which is the better migration target, RabbitMQ or Azure Service Bus?
- Before that choice, consider turning a database table into the queue. In most small and medium-sized business systems that use MSMQ, the party on the other end of the queue is a process that updates the company's own database. In that case, a table queue lets you commit "dequeuing the message" and "updating the business data" in the same local transaction as the business data, replacing the consistency that MSMQ plus a distributed transaction (DTC) used to provide with a much simpler mechanism. Only when a table queue falls short of a requirement — loosely coupled delivery across multiple systems, or the need for high throughput, for example — should you move on to considering RabbitMQ if the on-premises requirement is strong, or Azure Service Bus if it can live in the cloud. That ordering is the one least likely to fail.
- Is it acceptable to just leave things on .NET Framework for now?
- Conditionally, yes. .NET Framework 4.8 is supported as a Windows component, along the OS's own lifecycle, and MSMQ itself has not been deprecated, so you can reasonably expect it to keep running. That said, if you choose to leave it as-is, do at minimum these four things: state the enabling of the Message Queuing feature explicitly in your kitting procedure, put monitoring in place for queue length and the journal, document the message format and connection configuration, and leave a recovery procedure behind in case the person responsible moves on. The real risk of leaving it as-is is not technical — it is that nobody is left who can touch it.