Skip to content

mediator

Evaluating MediatR in Modern .NET (8, 9, 10) Applications

Introduction and Background

MediatR is a popular .NET library that implements the Mediator pattern for in-process messaging. It allows decoupling of request senders from their handlers by routing messages (requests/commands/queries) through a central mediator. Over the years it has become a staple in many “Clean Architecture” templates and CQRS-style projects, with over 286 million NuGet downloads as of 2025 (MediatR and MassTransit Going Commercial: What This Means For You). MediatR provides a simple request/response API and supports publish/subscribe for in-memory notifications, along with a pipeline behavior feature that enables plugging in cross-cutting concerns (like logging, validation, etc.) around those requests.

However, the landscape has changed in recent .NET versions (8 and beyond). The .NET platform now offers native features (such as minimal API endpoint filters, improved middleware, and powerful DI patterns) that cover many use cases MediatR was traditionally used for. Additionally, MediatR’s licensing has evolved – it is transitioning from a free OSS tool to a commercial (paid) product, raising questions about relying on it for core infrastructure (AutoMapper and MediatR Going Commercial) (MediatR and MassTransit Going Commercial: What This Means For You). This article provides a comprehensive evaluation of using MediatR in modern .NET applications, comparing its benefits and drawbacks to native alternatives, and offering guidance on when (if ever) it remains the right choice.

Overview: What MediatR Does

MediatR’s primary role is to reduce direct dependencies between components by providing a central mediator for interactions. Instead of a controller or service directly instantiating or calling a handler class, it sends a request to IMediator, which in turn finds and invokes the appropriate handler. This results in a clean separation: the sender only knows about the request and the mediator, not the concrete handler. MediatR supports:

  • Request/Response handling: e.g. a CreateOrderCommand is sent and a OrderCreatedResult is returned by the corresponding handler.
  • Notifications (pub/sub): one event can be published via the mediator to multiple in-process notification handlers.
  • Pipeline Behaviors: custom pre- and post-processing logic that wraps the handling of requests (similar to middleware but at the MediatR pipeline level).

Example – Using MediatR to handle a command with a pipeline behavior:

// Define a request (command) and its response
public record CreateOrderCommand(OrderDto Order) : IRequest<OrderResult>;

// Implement the handler for the request
public class CreateOrderHandler : IRequestHandler<CreateOrderCommand, OrderResult> {
    public async Task<OrderResult> Handle(CreateOrderCommand request, CancellationToken ct) {
        // Business logic to create order
        // ... (e.g., save to DB)
        return new OrderResult { Success = true, OrderId = /* new ID */ };
    }
}

// Define a pipeline behavior (e.g., logging) that wraps all requests
public class LoggingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> {
    public async Task<TResponse> Handle(TRequest req, RequestHandlerDelegate<TResponse> next, CancellationToken ct) {
        Console.WriteLine($"Handling {typeof(TRequest).Name}");
        var response = await next();  // call the next piece in pipeline (eventually the handler)
        Console.WriteLine($"Handled {typeof(TRequest).Name}");
        return response;
    }
}

In an ASP.NET Core app, once MediatR is configured (e.g. services.AddMediatR(...)), any component (like a controller or minimal API endpoint) can send the CreateOrderCommand via await _mediator.Send(command) and receive the result. The registered LoggingBehavior will run automatically before and after the handler, without the controller needing to explicitly call any logging logic.

Pros of Using MediatR (In-Process Messaging Benefits)

Using MediatR can provide several advantages in a modern .NET application:

  • Decoupling and Separation of Concerns: Senders of requests (UI layers or other services) do not need to know implementation details of handlers. This promotes a cleaner architecture where business logic is isolated. It’s especially useful in large systems where many different parts may request the same operations. MediatR “decouples requests from the objects handling them, promoting separation of concerns” (MediatR and MassTransit Going Commercial: What This Means For You). This decoupling can make it easier to refactor or change implementations without affecting the calling code ( Craig Mattson's Blog :: MediatR vs. Minimal APIs vs. FastEndpoints).

  • Consistent Structure (CQRS-friendly): MediatR encourages a use-case centric design: each request (command/query) is represented by a class and handled by a dedicated handler. This aligns naturally with the Command-Query Responsibility Segregation (CQRS) pattern, where commands and queries are distinct and handled separately (Stop Conflating CQRS and MediatR). Many teams find this one-class-per-use-case approach improves code organization (logic isn’t all lumped into large service classes).

  • Pipeline Behaviors (Middleware for Handlers): The pipeline behavior feature acts like an in-process middleware for requests (MediatR Pipeline Behaviors and Fluent Validation in NET 8 ...). You can write behaviors to handle cross-cutting concerns such as logging, validation, authorization, exception handling, caching, etc., and they will wrap around all (or specific) requests. This avoids duplicating such boilerplate in every handler. For example, one can implement a single validation behavior that automatically validates any request object that has validators associated, before it reaches its handler. This centralized approach is very powerful for consistent policies.

  • In-Process Publish/Subscribe: In addition to request/response, MediatR supports notifications (INotification) that multiple handlers can subscribe to. This is handy for domain events within a monolith: one part of the system can publish an event (e.g. OrderCreatedEvent) and multiple other parts can react to it (e.g., send email, update inventory, etc.), all in-memory. It provides a simple event aggregator pattern without setting up external message brokers.

  • Thin Controllers/Endpoints: By using MediatR, you can keep your controllers or API endpoints extremely thin. They often just translate an HTTP request to a MediatR call. Rather than injecting many services into a controller, you inject a single IMediator. This means the controller doesn’t need to know about all the dependencies required to handle a request – it simply hands off to the mediator. This can make the API layer very streamlined and focused purely on request/response mapping.

  • Testability: The mediator pattern can aid testing since handlers can be unit-tested in isolation (they are just classes implementing an interface, with their dependencies injected). The IMediator itself can be mocked if needed to test higher layers. Also, because of decoupling, you can swap out implementations or run handlers in isolation more easily.

  • Mature Ecosystem: MediatR has been around for many years with a stable API. There is plentiful documentation, examples, and extension libraries (for example, integration with FluentValidation via pipeline, etc.). Senior .NET developers likely have experience with it, and it’s battle-tested in many production systems.

In summary, MediatR can bring a clean, pattern-based approach to structuring business logic and handling cross-cutting concerns in-process. It shines in complex applications where decoupling internals improves maintainability or when implementing patterns like CQRS.

Cons and Pitfalls of Using MediatR

Despite its benefits, MediatR introduces additional abstraction that may not always be justified. Some common drawbacks and criticisms include:

  • Indirection and Complexity: MediatR adds an extra layer of indirection to calls. Instead of a straightforward method call from one class to another, you send a request through the mediator, which magically invokes the right handler. This can make the code harder to trace and reason about, especially for newcomers to the project (Stop Conflating CQRS and MediatR). The flow of logic is not explicit in the code; developers may need to jump between the request definition and its handler to understand what happens. Overusing MediatR (e.g. for every single small operation) can result in a codebase where it’s non-obvious how things tie together at runtime. As one author noted, “The indirection MediatR introduces is its most criticized aspect. It can make code harder to follow, especially for newcomers to the codebase.” (Stop Conflating CQRS and MediatR)

  • Boilerplate and Proliferation of Types: A MediatR-centric design tends to produce a lot of small classes: one for each request (command/query), one for each response (often), one handler per request, plus any number of behavior classes, notification classes, etc. While each piece is individually simple, the total number of files and types can grow large, which adds overhead in navigating and maintaining the code. In many cases, the amount of code ends up being similar to just writing services and methods directly ( Craig Mattson's Blog :: MediatR vs. Minimal APIs vs. FastEndpoints) – just split differently. Craig Mattson notes that you often “end up writing the same amount of code anyway – and you don’t get the same level of [clarity]” when using MediatR with lots of handlers and interfaces ( Craig Mattson's Blog :: MediatR vs. Minimal APIs vs. FastEndpoints).

  • Performance Overhead (Minor but Present): MediatR is essentially an in-memory dispatch, which is quite fast (it mostly uses reflection or compiled expressions internally to locate and invoke handlers). For most applications the overhead is negligible (microseconds per call). However, in extremely high-throughput scenarios or hot code paths, this extra hop and resolution might have a cumulative impact. Large enterprise applications with tens of thousands of operations per second might prefer to eliminate even small overheads. The indirection also makes it a bit harder to optimize or use advanced features like source generation (some newer mediator libraries use source generators to minimize overhead, whereas MediatR’s base version does runtime scanning). Overall, performance is usually not a major concern for typical usage, but it’s a consideration for very performance-sensitive code.

  • Hidden Dependencies (Implicit Wiring): While having thin controllers is nice, it also means the controller’s dependencies are “hidden” behind the mediator. This can be viewed as a Service Locator pattern in disguise – the mediator, via the DI container, finds and injects the actual services needed by the handler at runtime. Some consider service locator an antipattern because it obscures what components are being used (You Probably Don't Need to Worry About MediatR). For example, if a handler requires IRepository, an email sender, etc., the controller does not list those, so at the API level it's not obvious what the operation actually involves. This can complicate understanding the system’s true dependencies. (Jimmy Bogard has argued MediatR is not literally a service locator because it doesn’t arbitrarily resolve services, only handlers (You Probably Don't Need to Worry About MediatR), but the effect of indirection is similar in practice to some.)

  • Lack of Explicitness: In a similar vein, using MediatR means that the explicit method call hierarchy is lost. Instead of seeing in code “Controller calls ServiceA, which calls RepositoryB”, you see “Controller sends XRequest”. The actual call chain is determined by convention (which handler implements XRequest) rather than an explicit reference. This can violate the “Explicit Dependencies Principle” according to some purists (You Probably Don't Need to Worry About MediatR). It’s a trade-off: you gain flexibility and decoupling at the cost of clarity in the flow. Teams must decide if that trade-off is worthwhile. Well-established teams may mitigate this by naming conventions and documentation, but new team members might still face a learning curve.

  • Overuse and Misuse: It is easy to adopt MediatR everywhere (“when you have a hammer, everything looks like a nail”). Not every project or every component benefits from the mediator pattern. For simple CRUD applications or small codebases, adding this pattern can be over-engineering. If every controller call simply wraps a single handler with no real decoupling needed, MediatR might be an unnecessary abstraction. In some cases, developers have even tried to replace a traditional service layer entirely with MediatR, which can lead to a fragmented design where business logic is spread across dozens of handlers with no unifying interface. Overuse can also make debugging harder, as you have to inspect each handler class individually for logic.

  • Custom Abstraction Pitfalls: Some teams, in an attempt to keep their core “clean,” will create their own abstraction on top of MediatR (for example, an IMessageBus interface that internally calls MediatR). This often adds little value and can complicate maintenance. Creating a custom mediator abstraction means you now have to maintain that wrapper, and you might lose some capabilities of MediatR or make updates harder. Since MediatR is a well-known interface already (IMediator), adding another layer tends to be redundant – you could instead directly reference MediatR or, if you want to avoid locking into it, consider not using it at all. In short, over-abstracting the mediator pattern (writing your own mediators or wrapper interfaces everywhere) can lead to “unnecessary complexity” without much benefit (Don't use MediatR by default on all .NET projects). The GoatReview article “Don’t use MediatR by default” demonstrates how one can achieve a use-case-driven architecture without the library, suggesting that blindly inserting an extra messaging layer is often needless (Don't use MediatR by default on all .NET projects) ( Craig Mattson's Blog :: MediatR vs. Minimal APIs vs. FastEndpoints).

  • License and Dependency Risk: A new development as of 2023-2025 is that MediatR is no longer guaranteed to be free for future versions. The author announced he will be “commercializing MediatR” to ensure sustainability (AutoMapper and MediatR Going Commercial). This means upcoming major releases (post v12) are likely to require a paid license for use in businesses. Relying on a third-party for core application flow always carried some risk (if the maintainer stopped working on it, for example), but now there’s a tangible consideration of cost and license compliance. Using MediatR in your architecture means you either: stick with the last OSS version (missing out on future improvements), or budget for license fees (if you want the latest updates/support) (MediatR and MassTransit Going Commercial: What This Means For You). Some organizations may be averse to introducing paid dependencies at the heart of their system. It also serves as a reminder that any critical third-party library can change licensing or support terms, which is a risk factor for long-term projects. In contrast, leaning on built-in .NET framework features (which are supported by Microsoft and come with the platform) might offer more stability and no additional cost.

To summarize, MediatR should be used judiciously. As one article title succinctly put it: “Don’t use MediatR by default on all .NET projects” (Don't use MediatR by default on all .NET projects). Its usage should be justified by clear benefits in architecture or maintainability. Otherwise, modern .NET offers simpler options that might serve you better, as we discuss next.

MediatR vs. Native .NET Features (Endpoint Filters, Middleware, DI)

With .NET 6, 7, and now 8, the framework itself has evolved features that overlap with MediatR’s functionality for handling cross-cutting concerns and achieving separation. Senior .NET developers should consider these native approaches, as they often reduce external dependencies and can simplify the design. Here we compare MediatR’s model (request/handler + pipeline) with native .NET constructs:

Endpoint Filters (Minimal APIs) vs. MediatR Pipeline Behaviors

.NET 7 introduced Endpoint Filters for minimal APIs, and .NET 8 refined them further. Endpoint filters allow you to run code before and/or after the execution of a minimal API handler, in a way very similar to MediatR’s pipeline behaviors. They effectively act as middleware at the endpoint level.

For example, suppose we want to log and validate a request to create an order. Using MediatR, we might write a pipeline behavior for logging, and perhaps another for validation, that wrap the CreateOrderCommand handler. With minimal APIs and endpoint filters, we can achieve the same without a mediator:

// Define an order creation service for business logic
public interface IOrderService { Task<OrderResult> CreateOrderAsync(OrderDto dto); }

var app = WebApplication.CreateBuilder(args).Build();

// Minimal API endpoint with an endpoint filter for cross-cutting concerns
app.MapPost("/orders", async (OrderDto order, IOrderService svc) => {
        // Directly call the service to perform the action
        OrderResult result = await svc.CreateOrderAsync(order);
        return Results.Ok(result);
    })
   .AddEndpointFilter(async (context, next) => {
        // This code runs before the main handler
        var dto = context.GetArgument<OrderDto>(0);
        if (dto == null) return Results.BadRequest("Order data is required");
        Console.WriteLine($"[Request] Creating order {dto.Id}...");
        var result = await next(context);  // call the inner handler (the lambda above)
        // This code runs after the handler
        Console.WriteLine($"[Response] Order creation completed.");
        return result;
   });

app.Run();

In the above snippet, the endpoint filter does logging (and a null-check) before calling the IOrderService, and logs after. This is analogous to a MediatR LoggingBehavior. We didn’t need a mediator to achieve this separation – the filter cleanly encapsulates the cross-cutting logic. Endpoint filters can be reused or applied globally as needed, and multiple filters can be chained (just like multiple pipeline behaviors).

Advantages of Endpoint Filters: They are built-in, lightweight, and targeted. They integrate naturally with minimal APIs (which are increasingly favored in .NET 6+ for simple services). One LinkedIn discussion even noted that with minimal APIs plus endpoint filters, “MediatR is not necessary anymore… endpoint filters [do] pretty [much the] same thing with one less package dependency.” (Probably the best pattern that involves MediatR, FluentValidation &… | Mukesh Murugan) This highlights that the platform itself is catching up to what MediatR offered, at least in terms of wrapping logic around handlers.

If your project uses MVC controllers instead of minimal APIs, you have analogous features: you can use Action Filters or Result Filters (attributes or middleware) to achieve similar effects for cross-cutting concerns on controllers/actions. (In fact, MediatR pipeline behaviors were partly inspired by the concept of ASP.NET MVC filters, but applying them at the business logic layer). The difference with endpoint filters is they work with the new minimal API model and are very flexible (you can even inject services into filters or have them globally).

When to prefer filters over MediatR pipeline: In modern .NET 8 projects that use minimal APIs, endpoint filters provide a straightforward way to add cross-cutting behavior. They keep the code localized (near the endpoint definition) and avoid the need for extra libraries. Use them if you want to keep your stack minimal and leverage built-in capabilities. Filters are especially suitable when the cross-cutting logic is primarily about handling HTTP concerns (validation of request models, transforming responses, etc.), as opposed to deep business logic policies.

ASP.NET Core Middleware vs. MediatR Behaviors

ASP.NET Core’s request middleware pipeline (the app.Use... components that run for every HTTP request) is another place to handle cross-cutting concerns. Middleware operates at the HTTP request/response level, before it even reaches your API endpoint or controller. Common tasks like authentication, error handling, logging, and compression are typically done with middleware.

Many concerns that one might implement via MediatR behaviors could also be done in middleware. For example: - Logging: Instead of a MediatR logging behavior that logs each request handled by the mediator, you might log each incoming HTTP request (and outgoing response) via a middleware. This gives a high-level audit of all requests. - Exception Handling: It’s common to have a global exception handling middleware that catches unhandled exceptions and transforms them into an HTTP response (500 or a formatted error). This can replace the need for a MediatR behavior that wraps every request handler in a try-catch. - Performance timing, caching: These can often be handled by middleware or response caching mechanisms at the ASP.NET level.

The key difference is scope and granularity: Middleware sees the entire HTTP transaction but doesn’t inherently know about the specific MediatR request inside (if any). MediatR behaviors, on the other hand, see the specific request object and response at the business logic level. In practice, many cross-cutting concerns don’t need that specificity. For example, logging the fact that “Order X created” can be done in the handler itself or via a domain event, rather than at the mediator pipeline. Other things, like validating an incoming JSON payload, are often done via model binding or filters (or FluentValidation integration) rather than a MediatR pipeline.

Use middleware for: - Global policies that apply to all requests uniformly (auth, error handling, global logging, etc.). - Scenarios where the concern is not tied to a specific operation but to the request or response as a whole (e.g., adding a header to every response).

Limitations of middleware relative to MediatR: - Middleware doesn’t easily allow skipping or altering the execution of individual handlers beyond the HTTP level. MediatR behaviors can decide not to call the next handler based on business logic (though that’s rare; usually one would throw or return an error result). - Middleware can’t differentiate between different MediatR request types within the HTTP flow (it only sees the URL/path or other HTTP info). Pipeline behaviors know the actual request type class, which can be useful for very targeted behavior (e.g., “for all Create commands do X”).

In modern .NET, one might use a combination: rely on middleware for broad concerns and filters or code in handlers for specific ones. This often leaves little need for a dedicated MediatR pipeline layer.

Direct Service Calls & Dependency Injection vs. MediatR Requests

One of MediatR’s selling points is that you can request an operation without directly referencing the implementing class. But .NET’s built-in Dependency Injection (DI) already allows decoupling via interfaces. Rather than using a mediator to call a handler, you could define an interface for that functionality and inject it where needed. This is the classic approach of layering an application (Controller -> Service -> Repository, etc.). For example, instead of a CreateOrderCommand + handler, you might have an IOrderService with a method CreateOrder(dto) that the controller calls. The service can implement any needed logic, call repositories, publish events, etc., without a mediator in the middle.

Direct method calls and DI have some advantages in clarity: - It’s immediately obvious what gets called. If a controller calls orderService.CreateOrder(), you can navigate to that method definition directly. - Fewer moving parts: no pipeline, no request/response classes (you likely just pass a DTO or parameters). - Less ceremony for simple operations.

You can still achieve separation by interface. In a way, an IOrderService is itself an abstraction that decouples the controller from the implementation (much like a mediator decouples it from a handler). The difference is it’s one-to-one coupling (controller knows it’s calling an order service) versus mediated coupling (controller only knows mediator, which then decides who to call).

Orchestration logic: In more complex workflows, you might have one action trigger multiple operations. With MediatR, one might send a command and then publish notifications for other parts to react. With a service-based approach, the service could orchestrate those calls explicitly. For instance, OrderService.CreateOrder could internally call InventoryService.UpdateStock and EmailService.SendConfirmation directly (via DI). This is a straightforward procedural approach. The downside is the OrderService now needs references to those other services (increasing coupling), whereas with MediatR notifications, OrderService could just publish an OrderCreatedEvent and not care who handles it. That said, using events within the same process can also be done without MediatR – one can implement a simple in-memory event dispatcher or use C# events or another library (or even something like the Wolverine library for in-process messaging).

DI patterns for cross-cutting: Another point is that using pure DI, you can implement the decorator pattern or interceptors for cross-cutting concerns. For example, you could have IOrderService decorated by a LoggingOrderService : IOrderService that logs calls and then delegates to the real implementation. .NET’s DI container (with help from libraries like Scrutor) can wire this up so that whenever IOrderService is resolved, you actually get the logging decorator which wraps the real service. This achieves a similar effect to a pipeline behavior, though setting up many decorators can become cumbersome (and it’s not as generic as MediatR’s pipeline, which covers all requests in one go).

In summary, leveraging well-designed service classes and DI can cover 80-90% of what you might use MediatR for, especially in .NET 8/9 solutions. You organize your code by functionality (services, repositories, etc.), use filters/middleware for cross-cutting policies, and you get a simpler call flow. The GoatReview article demonstrates a side-by-side of implementing a use-case with and without MediatR – ultimately showing that a carefully crafted “use-case-centric architecture without relying on MediatR’s internal messaging” is feasible and can be cleaner for many projects (Don't use MediatR by default on all .NET projects).

Summary of Native Alternatives

  • Endpoint Filters (in minimal API): Replace many uses of pipeline behaviors for pre/post logic, especially in .NET ⅞ minimal API apps (Probably the best pattern that involves MediatR, FluentValidation &… | Mukesh Murugan). They reduce the need for an in-process mediator for the sake of cross-cutting concerns.
  • ASP.NET Middleware / Filters: Handle global or coarse-grained cross-cutting concerns (auth, error handling, etc.) without any mediator. Leverage the framework’s pipeline.
  • Direct Calls via DI: Call services or handlers directly through interfaces. Use traditional OOP patterns (interfaces, inheritance, events) to achieve decoupling rather than a mediator library. This often results in more straightforward code for simple scenarios.
  • Other Libraries: If a mediator pattern is still desired but you don’t want MediatR specifically (maybe due to licensing or overhead), there are alternatives. For instance, FastEndpoints (an open-source library) provides a minimal API + CQRS framework that can eliminate the need for controllers and MediatR by combining the concepts. Other lightweight mediator implementations or source-generated dispatchers exist (e.g., Martin Thamar’s Mediator or Wolverine for in-process and out-of-process messaging). Before adopting another library, though, evaluate if the native solutions suffice.

The Cost of Custom Abstractions (Avoiding Over-Engineering)

A specific pitfall to be wary of is creating custom abstraction layers around MediatR or similar patterns. Some architects attempt to future-proof their design by not referencing MediatR directly in their application core – for example, defining an interface IApplicationBus with methods like Send() and having an infrastructure implementation that calls MediatR. The intention is to be able to swap out MediatR if needed, or to make the core completely ignorant of external libraries.

In practice, this often introduces more complexity than it removes: - You end up writing a thin wrapper that likely has a 1:1 mapping to MediatR’s own interface. This is essentially duplicate code that needs to be maintained and understood. - If MediatR changes (especially now moving to commercial license), your wrapper might shield some impact but you still have to update the implementation. And if you decide to remove MediatR, you’ll now have to implement the mediator behavior yourself behind that interface anyway. - It can confuse developers: “Do I use IMediator or IApplicationBus? What’s the difference?” Unless very well documented, such abstractions can be unclear. - MediatR is already an abstraction (the mediator pattern) – wrapping an abstraction with another abstraction can lead to indirection hell. You might find yourself debugging through your wrapper only to hit the next layer (the actual MediatR call).

Custom mediator implementations: Another angle is writing your own mediator from scratch (instead of using MediatR). If your needs are simple, this is actually not too hard – e.g., one can write a service that maintains a dictionary of request types to handler functions and uses DI to resolve and invoke them. This avoids an external dependency and could be tailored to your exact requirements. The downside is you must implement (and test) features that MediatR already provides (caching of handler lookup, supporting multiple handlers for notifications, possibly a pipeline mechanism). It’s doable but you should only undertake this if you have a very good reason (such as avoiding the new license cost, or needing a very specific behavior). Milan Jovanović suggests “MediatR isn’t too complex to build on your own” and even recommends it as an exercise (MediatR and MassTransit Going Commercial: What This Means For You) – but keep in mind maintenance burden. A custom solution becomes your responsibility entirely.

Recommendation: Don’t add an abstraction layer just for the sake of it. If you decide to use MediatR, feel free to use it directly in your application code (it’s okay for your Application layer to depend on it if it’s a conscious choice). If you decide not to use it, then call your services or handlers directly. The middle ground of writing your own mediator abstraction should be reserved for cases where you have very unique requirements or are extremely concerned about vendor lock-in. In most cases, it’s better to either commit to the library or not use it at all, rather than adding needless indirection.

MediatR’s New Licensing – Weighing Third-Party Dependencies

It is important to highlight that MediatR (along with some other .NET staples like AutoMapper and MassTransit) is transitioning to a commercial license model as of 2025. The creator, Jimmy Bogard, announced this move to ensure he can sustainably maintain the library (AutoMapper and MediatR Going Commercial). Concretely, this means future versions (beyond the currently available ones) will likely require purchasing a license for use in projects (exact pricing and terms to be determined as of the announcement). Current open-source versions remain available (and Jimmy has indicated security patches will continue for a while), but new features or official support will be behind a commercial agreement (MediatR and MassTransit Going Commercial: What This Means For You).

Implications for your team: - Cost: If you plan to keep up-to-date with MediatR in .NET 8/9/10, you may need to allocate budget for it. For a large team or organization, this might be a minor expense relative to project budgets, but it must be justified (especially if similar functionality is achievable without cost). - Version Lock-in: If you choose not to pay, you might end up stuck on the last free version. This could be fine for now – MediatR is mature – but over a span of years, .NET 10+ might introduce changes that necessitate library updates. There’s a risk that using an old version eventually becomes a maintenance problem (if it doesn’t support a new .NET runtime feature or has bugs that won’t be fixed in OSS version). - Third-Party Risk: This situation underscores the general risk of relying on third-party libraries for critical architecture. If the library becomes paid or is discontinued, you face a tough choice: adapt your code or pay up. Some developers on forums have reacted by saying it “becomes too risky, both financially and strategically, to rely on open-source third-party libraries at all” for core functionality (.NET library MassTransit going commercial with V9 | Hacker News) (MediatR is going commercial #1105 - GitHub). That’s an extreme view, but it highlights a preference for using platform capabilities over external libraries when possible.

  • Community and Ecosystem: It’s possible that fewer new projects will adopt MediatR by default once it’s commercial (developers might choose alternatives or simpler designs to avoid the hassle). If community usage diminishes, community support (blogs, Q&A) might also slow down over time. On the other hand, a paid model might lead to a more professionally supported product if you do buy in. Your team should gauge whether having a support channel and active development (through a paid license) is valuable, or if using the free built-in approaches is more aligned with your strategy.

In short, the licensing change doesn’t immediately break anything – you can continue using MediatR in .NET 8 as is. But it is a strategic inflection point to consider: do we want our core application flow tied to a now-commercial tool, or can we achieve our goals with the rich toolset provided by .NET itself? Many teams are taking this opportunity to reevaluate the necessity of MediatR in new projects.

When Might MediatR Still Be Appropriate?

Given all the above, are there scenarios where using MediatR (even in .NET 8/9/10 era) is still a valid or optimal choice? The answer is “yes, in certain cases” – although those cases are narrower than before. Consider using (or continuing with) MediatR in situations like:

  • Complex, Modular Monoliths: If you have a large monolithic application split into distinct modules or bounded contexts, in-process messaging can decouple those modules. MediatR’s notification (pub-sub) model can allow one module to react to events from another without tight coupling. For example, in a modular monolith architecture, you might not want the Orders module to directly reference the Notifications module – using MediatR to publish an OrderPlacedEvent that the Notifications module handles keeps boundaries clear. While this can be implemented without MediatR, the library makes it trivial to set up. In such cases, the benefit of decoupling might outweigh the added complexity.

  • Extensive Cross-Cutting Requirements: If your application has a lot of sophisticated cross-cutting concerns that must be applied to many or all operations (e.g., detailed auditing, multi-step validation, transaction scoping, etc.) and you prefer to implement these in the mediator pipeline, MediatR provides a ready framework. For instance, pipeline behaviors that enforce business rules before every command can ensure consistency. Could you do it with filters or AOP? Possibly, but MediatR might result in a cleaner, more domain-specific implementation (since behaviors operate in the context of application requests, not just raw HTTP). If you already have a suite of behaviors (like logging, validation, authorization checks) working in MediatR, porting all that to another approach might not be worth it.

  • Team Familiarity and Existing Investment: If your development team is already highly familiar with MediatR and perhaps your codebase is built around it, there’s a non-trivial cost to rip it out. Provided you’re satisfied with its performance and you have a plan for the licensing (e.g., stick to current version or are willing to pay for an upgrade when needed), it might be perfectly reasonable to continue using it. There’s nothing inherently “broken” about MediatR on .NET 8 – it still works as it always has. So long as the team finds it valuable and it’s not causing maintenance headaches, you might choose to keep it, especially in an existing project. Just make sure everyone agrees on the rationale (for example: “We use MediatR because we rely on in-process events heavily and it saves us a lot of boilerplate”).

  • In-Process Messaging as a Design Choice: Some architectures treat in-process messaging as a first-class concept – almost like having a message bus inside the app. If you foresee potentially moving to a distributed system or want to design your app in a message-driven way, using MediatR could be a stepping stone. It enforces a certain discipline (even if everything is synchronous in-memory now, you think of interactions as messages). Later, you might replace the backend of the mediator with an actual messaging infrastructure. There are libraries (like MassTransit or NServiceBus) that can bridge in-process and out-of-process messaging, but MediatR itself is purely in-memory. Nonetheless, for some complex enterprise scenarios, having the mediator pattern in place can ease future transitions (with the caveat that MediatR itself won’t scale out, but it shapes your code in a way that switching to a real queue is easier). If this forward-looking design is a goal, you might still opt to start with MediatR for its convenient API and then gradually evolve.

  • Multiple UI Entry Points or Background Processing Needs: If the same business logic needs to be invoked from different entry points (say, an API, and also a background job, and also maybe a gRPC service), having that logic encapsulated in a MediatR handler can be handy. All those callers can just send the same request to the mediator. This avoids duplicating orchestration logic across different controllers or services. (Note: This can also be solved by calling a service method directly – e.g., both your controller and your Hangfire job could call OrderService.CreateOrder – so MediatR is not the only way. But some prefer the symmetry of using requests for this.)

In essence, MediatR might still be appropriate when its specific strengths line up with your requirements – i.e., when decoupling and in-process messaging are key architectural drivers, and the team is equipped to manage the abstraction. It’s less appropriate if you’re using it just because “that’s what the template did” or to achieve things that the framework now provides out-of-the-box.

Caution: Even if you identify with some of the above scenarios, consider scope. Perhaps only a part of your system truly benefits from MediatR (e.g., the domain event handling part), whereas your simple CRUD endpoints might not. You can adopt MediatR in a targeted manner – it’s not all-or-nothing. For example, you might decide to use MediatR for domain events and notifications in your core domain, but use regular controller->service calls for application commands. It’s perfectly fine to mix approaches as long as it’s kept understandable.

Decision Matrix: MediatR vs. Native Approaches

To assist in decision-making, below is a summary matrix of scenarios and the recommended approach in modern .NET:

Scenario / Requirement Recommended Approach
Simple CRUD or small-scale application – Few cross-cutting policies, straightforward logic. Avoid MediatR. Use controllers or minimal APIs calling services/repositories directly. Leverage model binding and validation attributes for input checking. MediatR would add unnecessary indirection.
Need for Cross-Cutting Concerns (logging, validation, etc. on all requests) without tying to MediatR. Use native pipeline: Endpoint Filters (for minimal API) or MVC Filters/Middleware. These can handle concerns globally or per endpoint, no third-party needed ([Probably the best pattern that involves MediatR, FluentValidation &…
Complex business workflows that require orchestrating multiple operations in sequence. Use service-layer orchestration. Create application services that call the needed components in order. If decoupling is needed, use domain events or callbacks. MediatR can be used, but often explicit calls are easier to follow in this case.
In-process Pub/Sub for Domain Events within a rich domain model (especially in a modular monolith). Consider MediatR’s notification feature. This is one area where MediatR shines – it’s simple and reliable for in-memory events. Alternatively, implement a lightweight domain events dispatcher yourself. If many modules or team boundaries are involved, MediatR can reduce coupling.
High sensitivity to performance (e.g., low-latency games, high-frequency trading apps, etc.). Avoid MediatR. The overhead is small, but in extreme cases even small allocations or delays matter. Writing direct calls or optimized patterns (including source-generated code) would be preferable.
Existing project heavily invested in MediatR (lots of handlers, behaviors, etc.). Continue with MediatR (for now). There is no urgent need to remove it if it’s working. Plan for the future by monitoring licensing changes: you might stay on the last OSS version or allocate budget for an upgrade. Meanwhile, evaluate if new parts of the system could use simpler patterns to reduce over-reliance on the mediator.
Greenfield (new) project in .NET 8/9 with experienced team, aiming for long-term maintainability and minimal external deps. Lean toward native features first. Design the API with filters/middleware and DI. Only introduce MediatR if a clear use case emerges (like needing in-memory pub-sub or avoiding injecting dozens of dependencies into controllers). Often, you’ll find you don’t need it.
Requirement for first-class support / SLAs for the mediator component (enterprise support needed). Possibly use MediatR with a commercial license, as it might come with support. Or look at other supported products. If internal SLAs are a concern, relying on built-in .NET is safest since it’s Microsoft-supported.

This matrix is a guide – the final decision should account for the specifics of your system and team. The key is to balance clarity vs. complexity and dependency vs. built-in at the core of your application.

Conclusion and Recommendations

MediatR has been a valuable tool in the .NET ecosystem, bringing the mediator design pattern to countless projects and helping developers build cleaner code architectures. In the context of .NET 8, 9, and beyond, its role should be re-considered in light of the powerful features now available in the framework itself and the shift towards minimal APIs and simpler pipelines.

For a senior .NET development team, the recommendation is to prioritize clarity and native capabilities. Use MediatR only if it demonstrably improves your solution’s design or maintainability. Often, you can achieve the same goals with built-in constructs: - Use minimal API endpoint filters or MVC filters for cross-cutting concerns instead of immediately reaching for pipeline behaviors (Probably the best pattern that involves MediatR, FluentValidation &… | Mukesh Murugan). - Use well-defined services and DI to call your business logic directly, keeping the flow explicit. - Reserve in-process messaging (like MediatR notifications) for scenarios where decoupling of components is a must-have, not just a nice-to-have.

Be mindful of the additional overhead – both in complexity and now in potential cost – that adding MediatR brings. Introducing any third-party into your core infrastructure is a strategic decision; with MediatR moving to a paid model, ensure the value it provides is worth that commitment (AutoMapper and MediatR Going Commercial). If your team is comfortable with MediatR and leveraging it in ways that truly benefit the project (for example, implementing a robust CQRS pattern with clear separation), it can still be a worthwhile tool. Just avoid reflexive use “because we always did it that way.”

Strategic insight: The trend in modern .NET is toward minimalism and performance – removing unnecessary layers and using source generation or compile-time features to avoid runtime overhead. We see hints that the .NET team is aware of the need for better eventing/mediator patterns in the framework (there have been discussions about a built-in eventing framework in .NET 9/10 (Epic: Eventing Framework in .NET 9 · Issue #53219 · dotnet/aspnetcore · GitHub) (Epic: Eventing Framework in .NET 9 · Issue #53219 · dotnet/aspnetcore · GitHub)), though nothing concrete is arriving in .NET 10 as of now (Epic: Eventing Framework in .NET 9 · Issue #53219 · dotnet/aspnetcore · GitHub). This means the platform might eventually cover MediatR-like scenarios in an official way. Until then, weigh the pros and cons carefully.

In conclusion, MediatR is no longer an automatic “yes” for modern .NET apps. It is one of many tools – use it when it clearly aligns with your needs (and you’re okay with its new licensing), but otherwise favor the simplicity and transparency of native .NET 8+ features. A balanced, case-by-case approach will yield the best architecture: keep things simple by default, and introduce the mediator pattern only to solve specific problems that genuinely require it.

References: The analysis above draws on community discussions and expert insights. For instance, developers have noted that minimal APIs with endpoint filters can achieve the same goals as MediatR with fewer dependencies (Probably the best pattern that involves MediatR, FluentValidation &… | Mukesh Murugan), and that MediatR’s indirection can make code harder to follow in large teams (Stop Conflating CQRS and MediatR). The decision to commercialize MediatR (AutoMapper and MediatR Going Commercial) has further prompted teams to re-evaluate using a third-party mediator. By understanding these factors, a senior team can make an informed decision aligning with both modern .NET capabilities and the project’s long-term interests.