dotnet/yarp · error · ArgumentException
The http client must be of type HttpMessageInvoker, not Http
Error message
The http client must be of type HttpMessageInvoker, not HttpClient
What it means
Thrown by HttpForwarder.SendAsync when the supplied httpClient is an HttpClient rather than an HttpMessageInvoker. The HttpClient.SendAsync overload fully buffers the response, which hurts streaming/performance and breaks gRPC. YARP requires HttpMessageInvoker to preserve streaming semantics (see dotnet/yarp#458).
Source
Thrown at src/ReverseProxy/Forwarder/HttpForwarder.cs:119
HttpTransformer transformer,
CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(context);
ArgumentNullException.ThrowIfNull(destinationPrefix);
ArgumentNullException.ThrowIfNull(httpClient);
ArgumentNullException.ThrowIfNull(requestConfig);
ArgumentNullException.ThrowIfNull(transformer);
if (RequestUtilities.IsResponseSet(context.Response))
{
throw new InvalidOperationException("The request cannot be forwarded, the response has already started");
}
// HttpClient overload for SendAsync changes response behavior to fully buffered which impacts performance
// See discussion in https://github.com/dotnet/yarp/issues/458
if (httpClient is HttpClient)
{
throw new ArgumentException($"The http client must be of type HttpMessageInvoker, not HttpClient", nameof(httpClient));
}
// "http://a".Length = 8
if (destinationPrefix is null || destinationPrefix.Length < 8)
{
throw new ArgumentException("Invalid destination prefix.", nameof(destinationPrefix));
}
ForwarderTelemetry.Log.ForwarderStart(destinationPrefix);
var activityCancellationSource = ActivityCancellationTokenSource.Rent(requestConfig?.ActivityTimeout ?? DefaultTimeout, context.RequestAborted, cancellationToken);
try
{
var isClientHttp2OrGreater = ProtocolHelper.IsHttp2OrGreater(context.Request.Protocol);
// NOTE: We heuristically assume gRPC-looking requests may require streaming semantics.
// See https://github.com/dotnet/yarp/issues/118 for design discussion.
var isStreamingRequest = isClientHttp2OrGreater && ProtocolHelper.IsGrpcContentType(context.Request.ContentType);View on GitHub (pinned to bd11867bee)
Solutions
- Pass an HttpMessageInvoker (or the cluster's configured HttpClient, which YARP wraps appropriately) instead of HttpClient.
- If using IHttpClientFactory, create an HttpMessageInvoker from the handler: new HttpMessageInvoker(handler, disposeHandler: false).
- Use the cluster's pre-configured HttpClient via IReverseProxyFeature.Cluster.HttpClient which is already an HttpMessageInvoker.
Example fix
// before var client = new HttpClient(); await forwarder.SendAsync(context, address, client, reqConfig, transformer, ct); // after var handler = new SocketsHttpHandler(); var invoker = new HttpMessageInvoker(handler, disposeHandler: true); await forwarder.SendAsync(context, address, invoker, reqConfig, transformer, ct);
Defensive patterns
Strategy: type-guard
Validate before calling
if (httpClient is HttpClient)
throw new ArgumentException("Use HttpMessageInvoker, not HttpClient.", nameof(httpClient));
await forwarder.SendAsync(context, address, (HttpMessageInvoker)httpClient, reqConfig, transformer, ct); Type guard
static bool IsMessageInvoker(object client) => client is HttpMessageInvoker and not HttpClient;
Try / catch
try { await forwarder.SendAsync(context, address, client, reqConfig, transformer, ct); }
catch (ArgumentException ex) when (ex.Message.Contains("HttpMessageInvoker"))
{ var invoker = new HttpMessageInvoker(handler, disposeHandler: true); await forwarder.SendAsync(context, address, invoker, reqConfig, transformer, ct); } Prevention
- Always pass an HttpMessageInvoker to IHttpForwarder.SendAsync.
- Use the cluster's pre-configured HttpClient (already an invoker) via IReverseProxyFeature.
- Review code that constructs HttpClient for forwarding.
When it happens
Trigger: Passing a new HttpClient() or an HttpClient-derived instance as the httpClient argument to IHttpForwarder.SendAsync (or IForwarder.SendAsync). The runtime type check (httpClient is HttpClient) trips and rejects it.
Common situations: Creating an HttpClient from IHttpClientFactory and passing it directly to the forwarder. Reusing a typed HttpClient for forwarding. Migrating code that used HttpClient generically.
Related errors
- The IReverseProxyFeature Destinations collection was not set
- Chosen destination has no model set: '{destination.Destinati
- The request cannot be forwarded, the response has already st
- Invalid destination prefix.
- An error occurred when reading the request body from the cli
AI-assisted analysis of dotnet/yarp@bd11867bee (2026-08-13).
Data as JSON: /api/errors/e20d67e4f60ebff2.
Report an issue: GitHub.