Large HTTP request bodies are common in modern APIs. Applications upload JSON documents, bulk records, telemetry batches, configuration data, and other structured payloads that can quickly become expensive to transmit over the network.
Compression can reduce the number of bytes sent between clients and servers, but it is not free.
The server and client must spend CPU time compressing and decompressing data. Compression can also affect latency, memory usage, throughput, and infrastructure costs.
That creates a practical engineering question:
When does HTTP request compression provide enough network savings to justify its CPU and latency overhead?
There is no universal answer. The result depends on payload size, payload structure, compression algorithm, compression level, network conditions, CPU availability, and request concurrency.
This article builds a practical benchmarking approach for large request payloads in ASP.NET Core and focuses on measuring the actual trade-offs rather than assuming that compression is always beneficial.
Why Request Compression Matters
Consider an API receiving a 10 MB JSON payload.
Without compression:
Client
|
| 10 MB
v
Network
|
v
ASP.NET Core
With compression:
Client
|
| 1.5 MB compressed
v
Network
|
v
ASP.NET Core
|
| Decompress
v
10 MB application payload
The network transfers significantly fewer bytes, but the server now has additional work to perform.
The overall request cost can be thought of as:
Total Request Cost
=
Compression Cost
+
Network Transfer Cost
+
Decompression Cost
+
Application Processing Cost
Compression is useful when reducing network transfer produces a meaningful benefit relative to the additional CPU and latency cost.
For large payloads, that trade-off can be very different from small request bodies.
Request Compression vs Response Compression
These concepts are easy to confuse.
Response Compression
The server compresses a response before sending it to the client.
Server
|
| Compress
v
Network
|
v
Client
ASP.NET Core provides response compression middleware for this scenario.
Request Compression
The client compresses the request body before sending it.
Client
|
| Compress
v
Network
|
v
Server
|
| Decompress
v
Application
This article focuses on the second scenario.
Request compression can be especially useful for APIs that receive large structured payloads over bandwidth-constrained or high-latency networks.
How HTTP Request Compression Works
HTTP uses the Content-Encoding header to indicate that a request body has been encoded.
For example:
POST /api/orders/import HTTP/1.1
Content-Type: application/json
Content-Encoding: gzip
Content-Length: 1523487
The server can determine how to decode the body from the Content-Encoding value.
The important distinction is:
Content-Type
describes the underlying media type.
Content-Encoding
describes how that representation has been encoded for transport.
A compressed JSON request can therefore have:
Content-Type: application/json
Content-Encoding: gzip
The server should decompress the request before application-level JSON processing.
Choose Payloads That Represent Real Workloads
A benchmark is only useful when the payload resembles the data the application actually receives.
For example, a synthetic payload containing repeated characters can compress extremely well:
{
"value": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
}
That is not representative of many real applications.
A more realistic payload might contain:
{
"customerId": 12345,
"createdAt": "2026-08-17T10:00:00Z",
"orders": [
{
"id": 1001,
"product": "Product A",
"quantity": 2,
"price": 149.99
}
]
}
For a meaningful benchmark, include payloads with different characteristics.
| Payload | Approximate Size | Data Characteristics |
|---|
| Small JSON | 10–50 KB | Typical API request |
| Medium JSON | 500 KB–2 MB | Batch request |
| Large JSON | 5–10 MB | Bulk operation |
| Very large JSON | 25+ MB | Import/data processing |
The actual sizes should reflect the application's workload.
Build a Simple ASP.NET Core Endpoint
A benchmark endpoint can accept a large request body.
For example:
app.MapPost("/api/import", async (
HttpRequest request) =>
{
using var reader = new StreamReader(request.Body);
var json = await reader.ReadToEndAsync();
return Results.Ok(new
{
ReceivedBytes = json.Length
});
});
This is deliberately simple.
The goal is to isolate transport and request-processing behavior rather than benchmark business logic.
For a production-oriented test, replace the artificial processing with representative application work.
Generate a Large Request
A simple .NET client can create a payload and send it to the API.
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
var orders = Enumerable.Range(1, 100_000)
.Select(i => new
{
Id = i,
CustomerId = i % 5000,
Product = $"Product-{i % 100}",
Quantity = (i % 5) + 1,
Amount = (i % 1000) + 10
})
.ToArray();
var json = JsonSerializer.Serialize(orders);
Console.WriteLine($"Original size: {Encoding.UTF8.GetByteCount(json)} bytes");
This gives the benchmark a reasonably structured JSON workload.
Do not assume that the resulting compression ratio will represent every production payload. Different data structures produce different results.
Compress the Request
For gzip, the request can be compressed before transmission.
using System.IO.Compression;
using System.Net.Http.Headers;
using System.Text;
var jsonBytes = Encoding.UTF8.GetBytes(json);
await using var output = new MemoryStream();
await using (var gzip = new GZipStream(
output,
CompressionLevel.Fastest,
leaveOpen: true))
{
await gzip.WriteAsync(jsonBytes);
}
var compressedBytes = output.ToArray();
using var content = new ByteArrayContent(compressedBytes);
content.Headers.ContentType =
new MediaTypeHeaderValue("application/json");
content.Headers.ContentEncoding.Add("gzip");
using var response = await client.PostAsync(
"https://localhost:5001/api/import",
content);
The important pieces are:
content.Headers.ContentType =
new MediaTypeHeaderValue("application/json");
content.Headers.ContentEncoding.Add("gzip");
The first describes the original content.
The second tells the server how the request body has been encoded.
Measure Compression Ratio
Before measuring request latency, calculate how much the payload actually shrinks.
double ratio =
(double)compressedBytes.Length / jsonBytes.Length;
double savings =
1.0 - ratio;
Console.WriteLine(
$"Compression ratio: {ratio:P2}");
Console.WriteLine(
$"Network savings: {savings:P2}");
For example, if a 10 MB payload becomes 2 MB:
Original: 10 MB
Compressed: 2 MB
Savings: 80%
That is a significant network reduction.
However, it does not automatically mean the compressed request is faster.
Benchmark CPU and Latency Together
A useful benchmark should capture at least:
Original payload size
Compressed payload size
Compression ratio
Compression duration
Decompression duration
Request latency
Server CPU
Client CPU
Error rate
Requests per second
A simple client-side timing measurement is:
var stopwatch = Stopwatch.StartNew();
using var response = await client.PostAsync(
"https://localhost:5001/api/import",
content);
stopwatch.Stop();
Console.WriteLine(
$"Request time: {stopwatch.ElapsedMilliseconds} ms");
This gives you end-to-end request time.
For production-oriented benchmarking, also measure compression and decompression separately.
Otherwise, you cannot determine whether additional latency comes from compression, networking, or application processing.
Compare Compression Levels
Gzip supports different compression levels.
A useful benchmark can compare:
CompressionLevel.Fastest
CompressionLevel.Optimal
The goal is not to find the theoretically smallest payload.
The goal is to determine which setting produces the best result for the application's workload.
For example:
| Compression | Payload Size | CPU Time | Request Latency |
|---|
| None | Measure | Lowest | Measure |
| Fastest | Measure | Measure | Measure |
| Optimal | Measure | Measure | Measure |
The actual numbers should come from your benchmark environment.
Do not publish generic performance percentages without measuring the specific payload and environment.
Network Conditions Change the Result
Compression becomes more attractive as network transfer becomes more expensive.
Imagine two environments.
High-Bandwidth Internal Network
High bandwidth
Low latency
Low packet loss
The CPU overhead of compression may outweigh the time saved during transmission.
Constrained Network
Limited bandwidth
Higher latency
Potentially expensive transfer
Reducing the request size can have a much larger impact.
This is why compression benchmarks should test more than localhost.
A localhost benchmark primarily measures CPU and serialization behavior. It does not accurately represent a real network.
Test Different Concurrency Levels
A single request does not reveal how compression behaves under load.
Run tests at different concurrency levels:
1 concurrent request
10 concurrent requests
50 concurrent requests
100 concurrent requests
The exact levels should match the application's expected workload.
At higher concurrency, compression can consume enough CPU to affect unrelated requests.
A system might therefore show:
Network bandwidth ↓
CPU utilization ↑
p95 latency ↑
That may or may not be an acceptable trade-off.
Benchmark Server-Side Decompression
Client-side compression is only half of the equation.
The server must decompress every compressed request.
A useful measurement model is:
Client Compression
|
v
Network Transfer
|
v
Server Decompression
|
v
JSON Parsing
|
v
Application Processing
If decompression consumes substantial CPU, the API's capacity can change even though the network requirement is lower.
This matters particularly for APIs that receive large batches at high request rates.
Avoid Buffering Huge Requests Unnecessarily
Large request bodies can create memory pressure if the entire payload is buffered repeatedly.
A simple implementation such as:
var json = await reader.ReadToEndAsync();
loads the complete request into memory.
That may be acceptable for a controlled benchmark, but it should not automatically be copied into a production endpoint handling arbitrarily large requests.
For large imports, consider streaming-oriented processing where the serialization format and application architecture allow it.
The benchmark should therefore test the same request-processing model that production will use.
Compare Gzip With Other Encodings
A useful experiment can compare more than one compression algorithm where the application's infrastructure supports them.
For example:
| Encoding | Primary Trade-off |
|---|
| None | Lowest CPU, highest transfer size |
| Gzip | Broad compatibility and balanced compression |
| Brotli | Potentially stronger compression with different CPU characteristics |
| Other supported codecs | Evaluate based on ecosystem and workload |
Do not assume that the algorithm producing the smallest payload is automatically the fastest.
Measure:
Compression ratio
CPU cost
Latency
Throughput
together.
A Practical Benchmark Matrix
A production-oriented experiment can use a matrix such as:
| Payload | Encoding | Level | Concurrency | Metrics |
|---|
| 500 KB | None | N/A | 1 | Latency, CPU |
| 500 KB | Gzip | Fastest | 1 | Size, latency, CPU |
| 5 MB | None | N/A | 10 | Throughput, CPU |
| 5 MB | Gzip | Fastest | 10 | Throughput, CPU |
| 5 MB | Gzip | Optimal | 10 | Throughput, CPU |
| 25 MB | None | N/A | 10 | Resource usage |
| 25 MB | Gzip | Fastest | 10 | Resource usage |
| 25 MB | Gzip | Optimal | 10 | Resource usage |
This produces enough data to understand where compression starts providing a measurable advantage.
Common Mistakes
Benchmarking Only Localhost
A localhost benchmark does not represent real network transfer.
Using Highly Repetitive Test Data
Artificially repetitive data can produce unusually favorable compression results.
Use production-shaped samples.
Measuring Only Payload Size
A smaller payload is not automatically a faster request.
CPU and latency matter too.
Measuring Only Average Latency
Compression can create tail-latency effects under load.
Measure p95 and p99 when possible.
Ignoring CPU Saturation
An API can save bandwidth while becoming CPU-bound.
Monitor server CPU throughout the experiment.
Buffering Unlimited Request Bodies
Large requests can create memory pressure.
Define sensible request-size limits and use streaming approaches where appropriate.
Comparing Algorithms Without Equivalent Conditions
Keep payloads, hardware, concurrency, and network conditions consistent.
Otherwise, the comparison becomes difficult to interpret.
Troubleshooting Unexpected Results
If compressed requests are slower than uncompressed requests, do not immediately conclude that compression is ineffective.
Check:
Payload size.
Compression ratio.
Network bandwidth.
Network latency.
Client CPU.
Server CPU.
Compression level.
Decompression time.
JSON parsing time.
Request concurrency.
A 200 KB request on a fast internal network may not benefit much from compression.
A 20 MB request sent over a constrained network can produce a very different result.
The workload determines the answer.
Production Best Practices
For APIs receiving large request payloads:
Measure before enabling compression globally.
Use production-shaped payloads in benchmarks.
Test multiple payload sizes.
Measure both network savings and CPU cost.
Measure p95 and p99 latency for high-throughput APIs.
Test under realistic concurrency.
Avoid unlimited request-body buffering.
Set sensible maximum request sizes.
Monitor CPU after enabling compression.
Document why a particular encoding and compression level were selected.
Consider network conditions when evaluating the results.
Do not assume response-compression behavior automatically applies to request compression.
Frequently Asked Questions
Does compressing every API request improve performance?
No. Compression reduces transfer size but adds CPU work. Whether it improves end-to-end performance depends on payload size, network conditions, CPU capacity, and concurrency.
Is gzip always the best choice?
No. Gzip is widely supported, but different algorithms can have different compression ratios and CPU characteristics. Benchmark the options relevant to your clients and infrastructure.
Should small JSON requests be compressed?
Not necessarily. For very small payloads, compression overhead may provide little benefit. A size threshold can be considered when the application has evidence that it improves the workload.
Does request compression reduce server memory usage?
Not automatically. The compressed network representation is smaller, but the server may still need to decompress and materialize the complete logical payload.
How should large file uploads be handled?
Large binary files often have different requirements from structured JSON requests. If the source data is already compressed, additional HTTP compression may provide little benefit while consuming CPU.
Conclusion
HTTP request compression is a trade-off, not a free performance optimization.
For large JSON and other compressible payloads, compression can significantly reduce network transfer. At the same time, it introduces CPU work on both sides of the request and can affect latency and throughput under concurrency.
The right decision should therefore come from measurement.
Benchmark uncompressed and compressed requests using realistic payloads, multiple sizes, realistic concurrency, and representative network conditions. Measure payload size, compression ratio, CPU usage, request latency, and throughput together.
For a production .NET API, the most useful result is not simply "compression saves 80% of bandwidth." It is a finding such as: for this workload and network environment, compression reduces transfer cost enough to justify its CPU overhead without violating the application's latency target.
That is the kind of benchmark result that can support an engineering decision rather than simply confirming that compression works.