Technology Sep 08, 2026 · 4 min read

HTTP/2 and HTTP/3 in C#: The Speed You Didn't Know You Were Missing

Hey performance-minded developers! 👋 Are you still using HTTP/1.1 without realizing it? Let's unlock the modern protocols that can dramatically improve your app's network performance! Quick Protocol Comparison Feature HTTP/1.1 HTTP/2 HTTP/3 Multiplexing ❌ ✅ ✅ Header Compres...

DE
DEV Community
by Nick
HTTP/2 and HTTP/3 in C#: The Speed You Didn't Know You Were Missing

Hey performance-minded developers! 👋

Are you still using HTTP/1.1 without realizing it? Let's unlock the modern protocols that can dramatically improve your app's network performance!

Quick Protocol Comparison

Feature HTTP/1.1 HTTP/2 HTTP/3
Multiplexing
Header Compression ✅ (HPACK) ✅ (QPACK)
Connection TCP TCP QUIC/UDP
Head-of-line Blocking Partial
0-RTT

🚀 Fun Fact: HTTP/3 uses QUIC, a protocol developed by Google that runs over UDP. It can establish connections up to 3x faster than TCP+TLS because it combines the transport and security handshakes into one round-trip!

Enabling HTTP/2

HTTP/2 is supported since .NET Core 3.0. Enable it:

var handler = new SocketsHttpHandler
{
    // Enable HTTP/2 explicitly
    EnableMultipleHttp2Connections = true
};

var client = new HttpClient(handler);

With HttpClientFactory

services.AddHttpClient<IMyApiClient, MyApiClient>()
    .ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler
    {
        EnableMultipleHttp2Connections = true
    });

Force HTTP/2

By default, HttpClient negotiates the protocol via ALPN. To force HTTP/2:

var request = new HttpRequestMessage(HttpMethod.Get, "https://api.example.com/data")
{
    Version = HttpVersion.Version20,
    VersionPolicy = HttpVersionPolicy.RequestVersionExact
};

var response = await client.SendAsync(request);
Console.WriteLine($"Protocol: {response.Version}"); // 2.0

Client-Level Default

var client = new HttpClient()
{
    DefaultRequestVersion = HttpVersion.Version20,
    DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrLower
};

Enabling HTTP/3

HTTP/3 support came in .NET 6 (preview) and .NET 7 (stable).

// .NET 7+
var client = new HttpClient()
{
    DefaultRequestVersion = HttpVersion.Version30,
    DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrLower
};

Per-Request HTTP/3

var request = new HttpRequestMessage(HttpMethod.Get, "https://cloudflare.com")
{
    Version = HttpVersion.Version30,
    VersionPolicy = HttpVersionPolicy.RequestVersionOrHigher
};

var response = await client.SendAsync(request);
Console.WriteLine($"Protocol: HTTP/{response.Version}"); // Might be 3.0!

Check Server Support

public static async Task<string> GetBestProtocolAsync(HttpClient client, string url)
{
    // Try HTTP/3 first
    try
    {
        var request = new HttpRequestMessage(HttpMethod.Head, url)
        {
            Version = HttpVersion.Version30,
            VersionPolicy = HttpVersionPolicy.RequestVersionExact
        };
        var response = await client.SendAsync(request);
        return $"HTTP/{response.Version}";
    }
    catch (HttpRequestException)
    {
        // Fall back to HTTP/2
    }

    try
    {
        var request = new HttpRequestMessage(HttpMethod.Head, url)
        {
            Version = HttpVersion.Version20,
            VersionPolicy = HttpVersionPolicy.RequestVersionExact
        };
        var response = await client.SendAsync(request);
        return $"HTTP/{response.Version}";
    }
    catch
    {
        return "HTTP/1.1";
    }
}

Why HTTP/2 is Faster

Multiplexing

HTTP/1.1 can only process one request per connection at a time. To get parallelism, browsers open 6+ connections per domain.

HTTP/2 multiplexes unlimited requests over a single connection:

// These all share ONE TCP connection with HTTP/2
var tasks = new[]
{
    client.GetAsync("/api/users"),
    client.GetAsync("/api/products"),
    client.GetAsync("/api/orders"),
    client.GetAsync("/api/settings"),
    client.GetAsync("/api/notifications")
};

await Task.WhenAll(tasks);
// Much faster than sequential HTTP/1.1!

Header Compression

HTTP headers are verbose and repetitive. HTTP/2 uses HPACK compression:

HTTP/1.1 headers per request: ~800 bytes
HTTP/2 after compression: ~20-50 bytes (after first request)

That's 95% smaller headers!

💡 Pro Tip: Multiple HTTP/2 Connections

Sometimes you want multiple HTTP/2 connections (different auth contexts, load balancing):

var handler = new SocketsHttpHandler
{
    EnableMultipleHttp2Connections = true,
    MaxConnectionsPerServer = 10 // Allow up to 10 HTTP/2 connections
};

Performance Tuning

Connection Pooling

var handler = new SocketsHttpHandler
{
    // HTTP/2 settings
    EnableMultipleHttp2Connections = true,
    InitialHttp2StreamWindowSize = 65536 * 16, // 1MB window

    // Connection lifetime
    PooledConnectionLifetime = TimeSpan.FromMinutes(15),
    PooledConnectionIdleTimeout = TimeSpan.FromMinutes(2),

    // Keep connections warm
    KeepAlivePingPolicy = HttpKeepAlivePingPolicy.WithActiveRequests,
    KeepAlivePingDelay = TimeSpan.FromSeconds(60),
    KeepAlivePingTimeout = TimeSpan.FromSeconds(30)
};

GRPC (Built on HTTP/2)

If you're using gRPC, you're already on HTTP/2:

var channel = GrpcChannel.ForAddress("https://api.example.com", new GrpcChannelOptions
{
    HttpHandler = new SocketsHttpHandler
    {
        EnableMultipleHttp2Connections = true,
        KeepAlivePingDelay = TimeSpan.FromSeconds(60),
        KeepAlivePingTimeout = TimeSpan.FromSeconds(30)
    }
});

Debugging Protocol Version

public class ProtocolLoggingHandler : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request,
        CancellationToken cancellationToken)
    {
        Console.WriteLine($"→ Request version: HTTP/{request.Version}");

        var response = await base.SendAsync(request, cancellationToken);

        Console.WriteLine($"← Response version: HTTP/{response.Version}");

        return response;
    }
}

Real-World Impact

I ran a benchmark fetching 100 resources from an API:

Protocol Time Connections
HTTP/1.1 4.2s 6
HTTP/2 1.1s 1
HTTP/3 0.9s 1

HTTP/2 was 4x faster with 6x fewer connections!

When to Use What

Protocol Best For
HTTP/1.1 Legacy systems, simple requests
HTTP/2 Most modern APIs, microservices, gRPC
HTTP/3 Mobile apps, high-latency networks, CDNs

Wrapping Up

Upgrading to HTTP/2 is usually a one-liner change that can significantly improve performance — especially for apps making many parallel requests to the same host.

HTTP/3 is the future, with even better performance on unreliable networks. Start testing it now!

Check your protocol version with response.Version and make sure you're not leaving performance on the table.

Happy speeding! 🚀

DE
Source

This article was originally published by DEV Community and written by Nick.

Read original article on DEV Community
Back to Discover

Reading List