HTTP payload size matters more than it first appears. Large JSON requests consume bandwidth, increase transfer time, and can become expensive when applications communicate across regions, mobile networks, or high-latency connections.
Compression can reduce the amount of data transmitted, but it is not free. The sender has to spend CPU time compressing the payload, and the receiver has to spend CPU time decompressing it. The right compression strategy therefore depends on the workload rather than simply choosing the algorithm with the highest compression ratio.
.NET has continued expanding its compression capabilities across the runtime and ASP.NET Core. Recent .NET 11 development includes additional compression-related improvements, making it a useful platform for evaluating request-compression trade-offs. Earlier .NET releases already provided request decompression support in ASP.NET Core, where compressed request bodies can be transparently decompressed by middleware.
This article focuses on the engineering question behind the feature: when does HTTP request compression actually improve an application's behavior, and when does the CPU cost outweigh the network savings?
Why HTTP Request Compression Matters
Consider an API that accepts a large JSON document:
{
"customerId": 10245,
"orders": [
{
"productId": 1001,
"quantity": 5,
"description": "A detailed product description..."
}
]
}
A small request may not benefit much from compression. For a large request containing repetitive JSON property names, strings, arrays, and other structured data, compression can substantially reduce the number of bytes transmitted.
The trade-off can be summarized as:
| Factor | Without Compression | With Compression |
|---|
| Network payload | Larger | Smaller |
| Upload bandwidth | Higher | Lower |
| Sender CPU | Lower | Higher |
| Receiver CPU | Lower | Higher |
| Compression latency | None | Additional |
| Best fit | Small/simple payloads | Large/repetitive payloads |
| Main risk | Network cost | CPU overhead |
The important point is that compression is an optimization across the entire request path, not just a serialization feature.
HTTP Request Compression vs Response Compression
These concepts are easy to confuse.
Response compression compresses data produced by the server before it is sent to the client.
Request compression compresses data produced by the client before it is uploaded to the server.
For example:
Client
|
| Compressed HTTP Request
v
ASP.NET Core API
|
| Decompress
v
Application
ASP.NET Core has supported request decompression middleware for clients that send compressed request bodies. When the Content-Encoding header identifies a supported encoding, the middleware wraps the request body in the appropriate decompression stream. Decompression occurs when the request body is read rather than eagerly processing the entire body up front.
That distinction matters when designing benchmarks because measuring only server-side request processing does not tell you the complete cost of compression.
Compression Algorithms to Evaluate
A meaningful benchmark should compare algorithms using the same payloads and request conditions.
Common compression formats available in the .NET ecosystem include:
Gzip
Deflate
Brotli
Zstandard
The .NET APIs expose these through the compression libraries and related HTTP functionality. The DecompressionMethods API, for example, includes GZip, Deflate, Brotli, and Zstandard in current .NET API documentation.
Each algorithm represents a different balance between compression speed and resulting payload size.
Brotli is a useful example because compression quality can be tuned. Higher compression levels can reduce output size further but require more processing. Microsoft has previously demonstrated that compression-level selection can produce very different CPU costs, reinforcing why a benchmark should measure both size and execution time rather than declaring one algorithm universally superior.
Designing a Useful Benchmark
A benchmark should answer more than:
"Which algorithm produces the smallest file?"
Instead, measure at least these dimensions:
Original payload size
Compressed payload size
Compression ratio
Compression time
Decompression time
End-to-end request latency
Requests per second
CPU utilization
Memory allocation
A simple compression-ratio calculation is:
Compression Ratio = Compressed Size / Original Size
For example, if a 10 MB payload becomes 2 MB:
2 MB / 10 MB = 0.20
The compressed representation is 20% of the original payload.
That does not automatically mean the application is faster. If compression adds enough CPU work to dominate the request, the smaller network payload may not compensate for it.
Building a Benchmark Payload
Use representative data rather than artificially repetitive strings.
For example, create a moderately large JSON document:
public sealed record Order(
int Id,
string Product,
int Quantity,
decimal Price);
public sealed record OrderBatch(
int CustomerId,
List<Order> Orders);
Generate multiple records:
var orders = Enumerable.Range(1, 50_000)
.Select(i => new Order(
i,
$"Product-{i % 500}",
(i % 10) + 1,
10 + (i % 100)))
.ToList();
var payload = new OrderBatch(
10001,
orders);
Serialize it using System.Text.Json:
var json = JsonSerializer.Serialize(payload);
var data = Encoding.UTF8.GetBytes(json);
Console.WriteLine($"Payload size: {data.Length:N0} bytes");
This gives the benchmark a deterministic starting point.
For a serious evaluation, create several payload classes instead of relying on one dataset:
| Payload | Purpose |
|---|
| Small JSON | Determine whether compression overhead dominates |
| Medium JSON | Represent typical API requests |
| Large JSON | Measure bandwidth savings |
| Highly repetitive JSON | Test compression efficiency |
| Random-like JSON | Test less-compressible data |
| Binary content | Determine whether compression is useful at all |
Measuring Compression with BenchmarkDotNet
BenchmarkDotNet is a practical choice for measuring compression overhead in isolation.
A simplified benchmark can look like this:
[MemoryDiagnoser]
public class CompressionBenchmark
{
private byte[] _payload = null!;
[GlobalSetup]
public void Setup()
{
_payload = File.ReadAllBytes("payload.json");
}
[Benchmark]
public byte[] Gzip()
{
using var output = new MemoryStream();
using (var gzip = new GZipStream(
output,
CompressionMode.Compress,
leaveOpen: true))
{
gzip.Write(_payload);
}
return output.ToArray();
}
[Benchmark]
public byte[] Brotli()
{
using var output = new MemoryStream();
using (var brotli = new BrotliStream(
output,
CompressionMode.Compress,
leaveOpen: true))
{
brotli.Write(_payload);
}
return output.ToArray();
}
}
The important detail is that the payload should be prepared during GlobalSetup. Otherwise, file I/O or payload generation can contaminate the measurement.
The benchmark should also avoid allocating unnecessary objects inside the measured operation unless those allocations are intentionally part of what you want to measure.
Measuring HTTP End-to-End Behavior
A compression microbenchmark is useful, but it does not represent a real HTTP request.
For an end-to-end test, create an ASP.NET Core API that accepts a request body and enable request decompression.
A simplified server configuration is:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRequestDecompression();
var app = builder.Build();
app.UseRequestDecompression();
app.MapPost("/orders", async (HttpRequest request) =>
{
using var reader = new StreamReader(request.Body);
var json = await reader.ReadToEndAsync();
return Results.Ok(new
{
ReceivedBytes = json.Length
});
});
app.Run();
The middleware allows the application to work with the decompressed request body rather than forcing each endpoint to implement decompression manually.
The client should send the appropriate Content-Encoding header so the server knows how the body has been encoded.
The benchmark should then compare:
Scenario A: Uncompressed request
Scenario B: Gzip request
Scenario C: Brotli request
Scenario D: Other supported compression format
The key measurement is not simply server execution time. Measure the complete request lifecycle.
Measuring the Network Trade-Off
A useful production-oriented model is:
Total Request Cost =
Serialization
+ Compression
+ Network Transfer
+ Decompression
+ Server Processing
Without compression:
Serialization
+
Network Transfer
+
Server Processing
With compression:
Serialization
+
Compression
+
Smaller Network Transfer
+
Decompression
+
Server Processing
Compression wins when the reduction in transfer cost is greater than the additional compression and decompression overhead.
This is why results can differ dramatically between environments.
A service communicating over a fast internal network may gain little from compression. The same service communicating between geographically distant regions or over constrained connections may benefit significantly.
HTTP Version Should Be Kept Consistent
When comparing compression strategies, do not accidentally change other networking variables.
HTTP/3, for example, uses QUIC rather than TCP and has different transport characteristics from HTTP/1.1 and HTTP/2.
Therefore, if the goal is specifically to benchmark compression, keep the HTTP version consistent across test scenarios.
Otherwise, the experiment can unintentionally measure multiple variables simultaneously.
For example:
Test A
HTTP/2 + Gzip
Test B
HTTP/3 + Brotli
A difference between those tests cannot reliably be attributed to compression alone.
Production Considerations
Choose Compression Based on Payload Size
Do not blindly compress every request.
For very small payloads, compression metadata and CPU work may provide little benefit.
A practical policy can be based on request size:
if (payload.Length < MinimumCompressionSize)
{
// Send without compression
}
else
{
// Compress request
}
The threshold should be determined through measurement rather than copied from another application.
Avoid Compressing Already-Compressed Data
Formats such as JPEG, PNG, MP4, ZIP, and many archive formats are already compressed.
Running another compression algorithm over such data can consume CPU while producing little size reduction.
Consider CPU Capacity
Compression shifts some work from the network to the CPU.
That trade can be desirable when bandwidth is constrained but undesirable when the application is already CPU-bound.
Monitor Both Sides
Client-side CPU and server-side CPU should both be measured.
The client performs compression:
Client CPU ↑
Network bytes ↓
The server performs decompression:
Network bytes ↓
Server CPU ↑
The optimization therefore needs to be evaluated across the complete system.
Common Benchmarking Mistakes
Benchmarking Only Compression Ratio
A smaller payload is not automatically a faster request.
Always measure compression time and end-to-end latency.
Using Only One Payload
Compression behavior depends heavily on the data.
Benchmark multiple payload sizes and structures.
Including Setup Work in the Measurement
Generating JSON, reading files, or creating random test data inside the benchmark can distort results.
Prepare test data before the measured operation.
Comparing Different Network Conditions
Running one test locally and another across a remote environment makes the results difficult to interpret.
Keep network topology, server hardware, HTTP version, and concurrency consistent.
Treating Preview Results as Production Benchmarks
.NET 11 preview APIs and behavior can change before final release.
A benchmark conducted against a preview should clearly identify the runtime build and test environment. It should be repeated against the final release before making production decisions.
Troubleshooting
Compressed Requests Are Rejected
Check the Content-Encoding header.
The server must know which encoding was used, and the corresponding decompression support must be configured.
Compression Reduces Bandwidth but Increases Latency
Measure CPU time.
If compression is expensive relative to the network savings, reduce compression levels, change algorithms, or apply compression only above a suitable payload threshold.
Results Change Between Runs
Check:
CPU frequency scaling
Background processes
Garbage collection
Network variability
Connection reuse
Payload generation
Concurrent request count
Run multiple iterations and use statistical summaries rather than relying on one request.
DNS or Connection Behavior Changes Results
Reuse HttpClient appropriately and control connection lifetime. HttpClient maintains connection pools, and Microsoft's guidance notes that connection lifetime can affect DNS refresh behavior and connection creation overhead.
Recommended Benchmark Matrix
For a serious engineering evaluation, use a matrix like this:
| Variable | Values |
|---|
| Payload size | Small / Medium / Large |
| Payload type | JSON / Text / Binary |
| Compression | None / Gzip / Brotli / Zstandard where supported |
| Compression level | Fast / Balanced / Maximum |
| HTTP version | HTTP/1.1 / HTTP/2 / HTTP/3 |
| Concurrency | 1 / 10 / 100 / Higher workload-specific levels |
| Network | Local / Latency-injected / Representative production path |
| Metrics | Size / CPU / Memory / Latency / Throughput |
The most useful result is not a single winner. It is a decision boundary showing which compression strategy works best for which workload.
Best Practices
Benchmark representative production payloads.
Measure both compressed size and CPU cost.
Include end-to-end network latency.
Test multiple payload sizes.
Keep HTTP version and connection behavior consistent.
Avoid compressing already-compressed formats.
Establish a minimum payload threshold.
Monitor client and server CPU.
Record the exact .NET runtime version and environment.
Revalidate preview benchmarks against the final runtime before production adoption.
Frequently Asked Questions
Does HTTP request compression always improve API performance?
No. Compression reduces network traffic but introduces CPU and processing overhead. It is most useful when network transfer is a meaningful part of total request cost.
Should every JSON request be compressed?
Not necessarily. Small JSON payloads may not justify the additional processing. Benchmark different payload sizes and establish an application-specific threshold.
Is Brotli always better than Gzip?
No. The appropriate choice depends on payload characteristics, compression settings, CPU capacity, and network conditions. Benchmark both using the workload your application actually handles.
Does request compression replace response compression?
No. They solve different problems. Request compression reduces client-to-server traffic, while response compression reduces server-to-client traffic.
Should compression be benchmarked separately from HTTP?
Both measurements are useful. Microbenchmarks isolate compression overhead, while end-to-end HTTP tests show whether those savings translate into better application behavior.
Conclusion
HTTP request compression is fundamentally a systems trade-off.
Reducing payload size can lower bandwidth consumption and network transfer time, but compression introduces additional CPU work on the client and decompression work on the server. The correct decision therefore cannot be based solely on compression ratio.
.NET 11 provides an interesting platform for evaluating these trade-offs as its networking and compression capabilities continue to evolve. The most valuable benchmark is one that measures the entire request path: payload size, compression cost, network transfer, decompression, latency, throughput, CPU, and memory.
For production systems, the goal should not be to find the universally "best" compression algorithm. The goal is to identify where compression provides a measurable net benefit for your specific workload.
That is the benchmark result that can actually guide an engineering decision.