Updated on
Wolverine is a .NET framework that works as both an in-process mediator (the role MediatR plays) and a full asynchronous messaging framework with RabbitMQ, Azure Service Bus, Amazon SQS, and Kafka transports (the role MassTransit plays).
Handlers are plain methods, no IRequest<T> interfaces and no base classes, because Wolverine wires them up with generated code instead of runtime reflection.
Since MediatR became dual-licensed at version 13.0.0 in July 2025 — the Reciprocal Public License 1.5 or a commercial licence — Wolverine has become the most common MIT-licensed migration target.
What Is Wolverine in .NET?
Wolverine is an open-source framework for command handling and asynchronous messaging in .NET, developed as part of the “Critter Stack” alongside the Marten document database. It covers two jobs that usually require separate libraries: in-process request/response mediation, where it replaces MediatR, and durable message-based communication over RabbitMQ, Azure Service Bus, Amazon SQS, or Kafka, where it competes with MassTransit and NServiceBus.
Instead of resolving handlers through reflection and a middleware pipeline, Wolverine uses Roslyn code generation to compile the dispatch path, so a handler is just a public method named Handle() whose first parameter is the message type. That runtime model is what sets Wolverine apart. The project’s own guide is emphatic: “there is zero runtime Reflection happening within the Wolverine execution runtime pipeline.”
That gives us less ceremony, easier unit testing (handlers are plain methods we can call directly), and measurably lower per-message overhead. Wolverine also ships production features that MediatR never had: a transactional inbox and outbox, sagas, scheduled messages, and retry policies.
Getting Started with Wolverine
Let’s start building our first application using the Wolverine library. We implement a new API project using the command:
dotnet new webapi -n IntroductionToWolverineLibrary
This creates a new minimal API project with the name IntroductionToWolverineLibrary and built-in OpenAPI support. Next, we install the Wolverine library:
dotnet add package WolverineFx
This installs the latest WolverineFx library to our project. At the time of writing this article, the most recent release line is WolverineFx 6.25.x.
Starting with the 6.x line, Wolverine ships its runtime Roslyn compiler as a separate package, so we install that one as well — without it, UseWolverine() throws at startup:
dotnet add package WolverineFx.RuntimeCompilation
Following, in the Program.cs file, we need to add:
builder.Host.UseWolverine();
This command makes Wolverine’s usages discoverable throughout our entire project. We are now ready to start building our app while exploiting the Wolverine library.
Wolverine Library as a Mediator
For demonstration purposes, we build the project as a book order management system. First, let’s create a simple object for a new book order:
public record Order(Guid OrderId, string CustomerName, int BookId);
Here we define a new record Order, designed for immutable data models. The object contains three fields. First, the OrderId, a Guid (Globally Unique Identifier) that identifies the order. Secondly, the CustomerName, a string represents the name of the customer. Lastly, the BookId, an int that identifies the book being ordered.
InvokeAsync() To Send a Message
Now, let’s set up our first endpoint in the Program.cs file. We’ll construct each of our endpoints using minimal API syntax:
app.MapPost("/order", async (Order newOrder, IMessageBus bus) => await bus.InvokeAsync(newOrder))
.WithName("NewBookOrder");
Here, we define an HTTP POST endpoint at the route /order. We configure the endpoint to accept an Order object and an IMessageBus instance as parameters. Finally, the provided lambda function asynchronously executes the InvokeAsync() method on the bus, passing the newOrder as an argument.
The call to WithName("NewBookOrder") names this endpoint NewBookOrder, which can be useful for routing or documentation purposes.
Now, let’s build the receiver method of the message:
public class OrderHandler
{
public static void Handle(Order? order) => Console.WriteLine($"New order received: {order}");
}
In a new class, we implement the method Handle that takes an Order object as a parameter and prints a message to the console, indicating that a new order has been received. By defining a method named Handle that takes an Order parameter, Wolverine recognizes this method as the handler for Order messages. When a new order message is received, Wolverine routes it to this method for processing using the .NET local message bus.
When Wolverine integrates into a .NET Core application, it registers the IMessageBus service in the underlying system IoC container, enabling its injection into controller classes or Minimal API endpoints, as demonstrated above. Then, the IMessageBus.InvokeAsync(message) method processes the provided message by determining the appropriate execution path for the message type and executing the corresponding Wolverine handler(s).
We don’t need to use interfaces to declare the intent of certain classes, such as IRequest or IHandler. Wolverine leverages Roslyn for Runtime Code Generation to minimize the code typically required to connect the correct handler to the appropriate command behind the scenes. Consequently, we can test the methods more easily. Instead of mocking an abstraction, we can directly test the method containing the business logic. Since the Handle method is a pure function, we can invoke and test it without dependencies.
Use of InvokeAsync<T>() With Return Object
In case we want our handler to return an object, we need to use the IMessageBus.InvokeAsync<T>() method:
app.MapPost("/orderReply", async (Order newOrder, IMessageBus bus) =>
await bus.InvokeAsync<string>(newOrder))
.WithName("NewBookOrderReply");
By using the IMessageBus.InvokeAsync<T>(message) overload, we expect the handler to return an object of type T. In our example, we expect a simple string response.
Now, let’s update the Handle method to return a response:
public static string Handle(Order? order) => $"New order received: {order}";
This method returns a confirmation string that we received a new order. It’s worth mentioning that we can modify the Handle method to return an object class or structure.
Using Queues with the Wolverine Library
The Wolverine library lets us use queues to send and receive our messages. In this way, we can communicate with different entities through a channel, like microservices or APIs.
Let’s set up the configuration to send messages to a specific queue:
builder.Host.UseWolverine( x =>
{
x.PublishAllMessages().ToLocalQueue("book-ordering");
});
In the Program.cs file, we configure the messages to be transmitted through a local queue with the name book-ordering. We ensure that any message we send to Wolverine’s IMessageBus will be processed according to Wolverine’s configured rules.
Similarly, let’s create a new BookReview object:
public record BookReview(int BookId, string ReviewerName, int Rating);
This simple record holds information about a new book review. Now, let’s set up the new endpoint:
app.MapPost("/bookReview", async (BookReview review, IMessageBus bus) =>
{
await bus.PublishAsync(review);
return Results.Ok("Book review submitted successfully.");
})
.WithName("BookReviewEndpoint");
When we make a POST request to this endpoint, the application asynchronously publishes this review using Wolverine’s IMessageBus. After publishing the message, the endpoint returns an HTTP response indicating the successful submission of the book review. This setup allows for decoupled handling of book reviews, leveraging Wolverine’s capabilities for message-based communication and processing within the .NET application architecture.
We publish this message to all queue subscribers. This is a common best practice when we want to raise event-like messages. This is especially necessary in an architecture where we want to process messages asynchronously and independently from the transmitter.
Now, let’s proceed with the handler implementation:
public class BookReviewHandler
{
public static void Handle(BookReview? review) => Console.WriteLine($"New book review received: {review}");
}
This handler is invoked after the message transmission since the method’s name is Handle and its argument is BookReview type. In this method, we similarly process the BookReview by generating a string that acknowledges receipt of the review.
Let’s check the logs in the console:
Successfully processed message IntroductionToWolverineLibrary.Models.BookReview#01909954-3efa-401e-b0ac-f93465bbadc4 from local://local-queue/
Wolverine produces a log when it successfully processes a message. As we can see here, the log indicates that Wolverine has successfully processed a message of type BookReview from the IntroductionToWolverineLibrary.Models namespace. The message source is the local://local-queue/, indicating also that it is handled from a local queue within the application.
External Queues For Handling Messages
Wolverine provides the capability to use external queue providers to handle messaging. For example, we can integrate with popular messaging systems such as RabbitMQ, Apache Kafka, Azure Service Bus, and Amazon SQS. Subsequently, this allows Wolverine to leverage these platforms’ robust and scalable messaging infrastructures, enabling seamless asynchronous communication and distributed processing within our applications.
By utilizing Wolverine to both publish and process messages through an external infrastructure, we can handle complex messaging patterns, ensure reliable message delivery, and scale our messaging architecture to meet the demands of high-traffic environments.
How Does Wolverine Compare to MediatR in C#?
Both libraries dispatch a message object to a handler, but they differ in mechanism and scope. MediatR requires the message to implement IRequest<TResponse> and the handler to implement IRequestHandler<TRequest, TResponse>; it resolves handlers through the DI container at runtime and stays strictly in-process.
Wolverine drops the interfaces entirely. Any public Handle() or Consume() method is discovered by convention and connected with generated code, and the same handler can later receive messages from an external broker without changing shape. Middleware works differently, too: MediatR pipelines are IPipelineBehavior classes, while Wolverine composes middleware into the generated code, including conventional transaction and error-handling policies.
The practical trigger for switching is licensing. From version 13.0.0 in July 2025, MediatR is dual-licensed: accept the Reciprocal Public License 1.5 and release our own source, meet all four Community-tier conditions, or buy a commercial licence. WolverineFx stays MIT with no conditions at all.
Migration is mostly mechanical: delete the marker interfaces, rename, and rewire registration.
| Criterion | Wolverine | MediatR | MassTransit |
|---|---|---|---|
| In-process mediator | Yes | Yes | No |
| Message broker transports | RabbitMQ, Azure Service Bus, SQS, Kafka | No | RabbitMQ, Azure Service Bus, SQS, Kafka |
| Handler style | Plain method, code generation | IRequestHandler<T> interface, reflection | IConsumer<T> interface |
| Durable outbox/inbox | Built in (PostgreSQL, SQL Server) | No | Built in |
| Sagas | Yes | No | Yes |
| License | MIT, unconditional (WolverineFx 6.25.x) | Dual-licensed from v13.0.0 (2025-07-02): RPL-1.5 or commercial (MediatR 14.2.0); free Community tier only if all four conditions are met; v12.5.0 and earlier stay Apache-2.0 | Commercial and source-available from v9 (9.0.0, 2026-01-06), runtime licence key required; v8 and earlier stay Apache-2.0 permanently and still ship (8.5.10), but the vendor calls them unsupported with no support-window date; under $1M gross annual revenue may qualify for a 100% discount, commercial support excluded |
For the pattern Wolverine replaces on the mediator side, see our guide on CQRS and MediatR in ASP.NET Core. On the messaging side, we have a comparison of Rebus, NServiceBus, and MassTransit, and a hands-on guide to using MassTransit with RabbitMQ.
Conclusion
In this article, we introduced the Wolverine library for handling messaging within our .NET application. We demonstrated, through an API how to set up endpoints, configure queues for message transmission, and implement handlers for asynchronous message processing.
Tested with .NET 10.0.10 and WolverineFx 6.25.5.
