{"record":{"id":"e20d67e4f60ebff2","repo":"dotnet/yarp","slug":"the-http-client-must-be-of-type-httpmessageinvoker","errorCode":null,"errorMessage":"The http client must be of type HttpMessageInvoker, not HttpClient","messagePattern":"The http client must be of type HttpMessageInvoker, not HttpClient","errorType":"exception","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"src/ReverseProxy/Forwarder/HttpForwarder.cs","lineNumber":119,"sourceCode":"        HttpTransformer transformer,\n        CancellationToken cancellationToken)\n    {\n        ArgumentNullException.ThrowIfNull(context);\n        ArgumentNullException.ThrowIfNull(destinationPrefix);\n        ArgumentNullException.ThrowIfNull(httpClient);\n        ArgumentNullException.ThrowIfNull(requestConfig);\n        ArgumentNullException.ThrowIfNull(transformer);\n\n        if (RequestUtilities.IsResponseSet(context.Response))\n        {\n            throw new InvalidOperationException(\"The request cannot be forwarded, the response has already started\");\n        }\n\n        // HttpClient overload for SendAsync changes response behavior to fully buffered which impacts performance\n        // See discussion in https://github.com/dotnet/yarp/issues/458\n        if (httpClient is HttpClient)\n        {\n            throw new ArgumentException($\"The http client must be of type HttpMessageInvoker, not HttpClient\", nameof(httpClient));\n        }\n\n        // \"http://a\".Length = 8\n        if (destinationPrefix is null || destinationPrefix.Length < 8)\n        {\n            throw new ArgumentException(\"Invalid destination prefix.\", nameof(destinationPrefix));\n        }\n\n        ForwarderTelemetry.Log.ForwarderStart(destinationPrefix);\n\n        var activityCancellationSource = ActivityCancellationTokenSource.Rent(requestConfig?.ActivityTimeout ?? DefaultTimeout, context.RequestAborted, cancellationToken);\n        try\n        {\n            var isClientHttp2OrGreater = ProtocolHelper.IsHttp2OrGreater(context.Request.Protocol);\n\n            // NOTE: We heuristically assume gRPC-looking requests may require streaming semantics.\n            // See https://github.com/dotnet/yarp/issues/118 for design discussion.\n            var isStreamingRequest = isClientHttp2OrGreater && ProtocolHelper.IsGrpcContentType(context.Request.ContentType);","sourceCodeStart":101,"sourceCodeEnd":137,"githubUrl":"https://github.com/dotnet/yarp/blob/bd11867bee7df522e7fd3effb08a9c85fd616908/src/ReverseProxy/Forwarder/HttpForwarder.cs#L101-L137","documentation":"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).","triggerScenarios":"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.","commonSituations":"Creating an HttpClient from IHttpClientFactory and passing it directly to the forwarder. Reusing a typed HttpClient for forwarding. Migrating code that used HttpClient generically.","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."],"exampleFix":"// before\nvar client = new HttpClient();\nawait forwarder.SendAsync(context, address, client, reqConfig, transformer, ct);\n// after\nvar handler = new SocketsHttpHandler();\nvar invoker = new HttpMessageInvoker(handler, disposeHandler: true);\nawait forwarder.SendAsync(context, address, invoker, reqConfig, transformer, ct);","handlingStrategy":"type-guard","validationCode":"if (httpClient is HttpClient)\n    throw new ArgumentException(\"Use HttpMessageInvoker, not HttpClient.\", nameof(httpClient));\nawait forwarder.SendAsync(context, address, (HttpMessageInvoker)httpClient, reqConfig, transformer, ct);","typeGuard":"static bool IsMessageInvoker(object client) => client is HttpMessageInvoker and not HttpClient;","tryCatchPattern":"try { await forwarder.SendAsync(context, address, client, reqConfig, transformer, ct); }\ncatch (ArgumentException ex) when (ex.Message.Contains(\"HttpMessageInvoker\"))\n{ var invoker = new HttpMessageInvoker(handler, disposeHandler: true); await forwarder.SendAsync(context, address, invoker, reqConfig, transformer, ct); }","preventionTips":["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."],"tags":["forwarder","httpclient","httpmessageinvoker","streaming","performance"],"backgroundTag":null,"analyzedSha":"bd11867bee7df522e7fd3effb08a9c85fd616908","analyzedAt":"2026-08-13T21:29:49.359Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}