ASP.NET Core  

Benchmarking .NET Compression for Large API Request Payloads

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.

PayloadApproximate SizeData Characteristics
Small JSON10–50 KBTypical API request
Medium JSON500 KB–2 MBBatch request
Large JSON5–10 MBBulk operation
Very large JSON25+ MBImport/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:

CompressionPayload SizeCPU TimeRequest Latency
NoneMeasureLowestMeasure
FastestMeasureMeasureMeasure
OptimalMeasureMeasureMeasure

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:

EncodingPrimary Trade-off
NoneLowest CPU, highest transfer size
GzipBroad compatibility and balanced compression
BrotliPotentially stronger compression with different CPU characteristics
Other supported codecsEvaluate 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:

PayloadEncodingLevelConcurrencyMetrics
500 KBNoneN/A1Latency, CPU
500 KBGzipFastest1Size, latency, CPU
5 MBNoneN/A10Throughput, CPU
5 MBGzipFastest10Throughput, CPU
5 MBGzipOptimal10Throughput, CPU
25 MBNoneN/A10Resource usage
25 MBGzipFastest10Resource usage
25 MBGzipOptimal10Resource 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:

  1. Payload size.

  2. Compression ratio.

  3. Network bandwidth.

  4. Network latency.

  5. Client CPU.

  6. Server CPU.

  7. Compression level.

  8. Decompression time.

  9. JSON parsing time.

  10. 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:

  1. Measure before enabling compression globally.

  2. Use production-shaped payloads in benchmarks.

  3. Test multiple payload sizes.

  4. Measure both network savings and CPU cost.

  5. Measure p95 and p99 latency for high-throughput APIs.

  6. Test under realistic concurrency.

  7. Avoid unlimited request-body buffering.

  8. Set sensible maximum request sizes.

  9. Monitor CPU after enabling compression.

  10. Document why a particular encoding and compression level were selected.

  11. Consider network conditions when evaluating the results.

  12. 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.