Two Biggest MediatR Mistakes

MediatR is useful at an application boundary: a caller sends one request, and one handler owns that use case. Two common shortcuts weaken that model—sending more requests from inside a handler and combining several request contracts in one handler class.
Both shortcuts look like ways to reuse dependencies or avoid small classes. In practice, they hide orchestration, make navigation harder, and couple use cases that should be independently testable and replaceable. This article explains the failure modes and the simpler boundaries that avoid them.
Mistake 1: Calling One MediatR Handler from Another
Consider the following situation. Suppose you need to handle a message:
public class PingHandler : IRequestHandler<Ping, string>
{
public Task<string> Handle(Ping request, CancellationToken token = default) =>
Task.FromResult("Pong");
}
Then a new requirement arrives: after handling this message, you need to process other messages based on either the original handler's result or the incoming message. One tempting implementation looks like this:
public class PingHandler : IRequestHandler<Ping, string>
{
private readonly IMediator _mediator;
public PingHandler(IMediator mediator) => _mediator = mediator;
public async Task<string> Handle(Ping request, CancellationToken token = default)
{
await _mediator.Send(new AfterPing(request), token);
return "Pong";
}
}
Keep this orchestration out of the handler.
First, it is confusing. MediatR already makes IntelliSense navigation harder; there is no need to make it worse.
It is also worth remembering that, by design, MediatR serves as an external bridge to your application's actual behavior—behavior that is specific to the domain you have chosen. That is precisely what the cover image illustrates.
Calling other messages from inside a handler is therefore an anti-pattern in the context of the library itself. In the words of MediatR's author:
The indirection of a handler is good at the application level, but just got confusing once we got inside a handler (and it introduced coupling).
The tool exists to provide request-response entry points at the top level of the application while minimizing coupling. The code above does exactly the opposite.
There are several ways to keep the orchestration visible:
Return the data needed to construct the next message, then send it at the higher level where the original handler was called—the simplest option.
Move the shared logic into an application or domain service.
Use an extension method for genuinely local, stateless reuse.
Use a MediatR pipeline behavior for cross-cutting concerns.
Mistake 2: Handling Multiple MediatR Requests in One Handler
Suppose you have a group of messages that work with the same entity. They have roughly the same dependencies, so instead of writing several handlers, you combine them into one and end up with something like this:
public class MyEntityRequestHandler :
IRequestHandler<CreateMyEntityRequest, MyEntity>,
IRequestHandler<GetMyEntityRequest, MyEntity>,
IRequestHandler<GetAllMyEntitiesRequest, List<MyEntity>>;
Once again, this goes against the library's core idea: splitting an application into a set of distinct requests to improve flexibility and maintainability. This approach takes us back to bloated interfaces and violates the Interface Segregation Principle (ISP).
Again, in the library author's words:
Don't combine your handlers, keep them separate, reduce coupling across handlers.
Better MediatR Handler Boundaries
Keep orchestration visible at the application boundary, and keep one handler focused on one request contract. Shared behavior can still live in domain services, application services, decorators, or pipeline behaviors; it does not need to be hidden behind another mediator call.
These are design rules, not syntax rules. If a workflow genuinely coordinates several use cases, model that workflow explicitly and test it as such. The warning is against accidental handler graphs whose control flow can be discovered only by tracing Send calls.
MediatR Handler FAQ
Should a MediatR handler call another handler?
Usually, no. Put workflow orchestration at the application boundary and move reusable behavior into an application service, domain service, decorator, or pipeline behavior. That keeps the sequence visible and prevents handlers from becoming an implicit call graph.
Can one class implement several IRequestHandler interfaces?
The type system allows it, but sharing a class couples request contracts that should be independently navigable, testable, and replaceable. Separate handler classes can still depend on the same lower-level service when they genuinely share behavior.
When should I use a MediatR pipeline behavior?
Use a pipeline behavior for concerns that consistently wrap many requests, such as validation, logging, or transaction handling. Do not use it to hide a business workflow whose sequence is important to the use case.
You can read more about the subject in these sources:
Follow Stepami on GitHub for open-source C#/.NET projects, compiler experiments, production-focused examples, and new releases.

