Introduction
HTTP compression is one of those optimizations that sounds simple: make the response smaller before sending it to the client.
For JSON APIs, this can be particularly useful because large JSON documents often contain repeated property names, strings, and structural characters that compress well. A smaller response means fewer bytes need to travel across the network.
But compression is not free.
The server has to spend CPU time compressing the response, and the client has to spend CPU time decompressing it. The real question is therefore not simply whether compression reduces response size. It is whether the reduction in network traffic is worth the additional processing cost for a particular workload.
ASP.NET Core provides response compression middleware for this purpose. With .NET 11, HTTP compression improvements make this an appropriate area for controlled benchmarking, especially for APIs that return large JSON responses.
This article demonstrates how to build a repeatable test and evaluate the trade-off between payload size, response time, CPU usage, and throughput.
Why HTTP Compression Matters for JSON APIs
Consider an API that returns a large collection of products:
{
"products": [
{
"id": 1001,
"name": "Wireless Keyboard",
"category": "Accessories",
"description": "A detailed product description..."
}
]
}
A response containing thousands of records can become quite large.
Without compression:
Application
|
v
Large JSON
|
v
Network
|
v
Client
With compression:
Application
|
v
Large JSON
|
v
Compress
|
v
Smaller response
|
v
Network
|
v
Decompress
|
v
Client
The network transfers fewer bytes, but the server performs additional work.
That creates a trade-off.
How ASP.NET Core Response Compression Works
ASP.NET Core provides response compression middleware.
A basic configuration looks like this:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddResponseCompression();
builder.Services.AddControllers();
var app = builder.Build();
app.UseResponseCompression();
app.MapControllers();
app.Run();
The middleware examines the request and response and determines whether compression should be applied.
The client typically communicates its supported compression formats through the Accept-Encoding header.
For example:
Accept-Encoding: gzip, br
The server can then select an appropriate encoding supported by both sides.
Creating a Large JSON Endpoint
For benchmarking, it helps to have a predictable endpoint that produces a sufficiently large response.
For example:
[ApiController]
[Route("api/products")]
public class ProductsController : ControllerBase
{
[HttpGet]
public IActionResult GetProducts()
{
var products = Enumerable.Range(1, 5000)
.Select(id => new Product
{
Id = id,
Name = $"Product {id}",
Category = "Electronics",
Description =
"This is a sample product description " +
"used for HTTP compression testing."
});
return Ok(products);
}
}
public class Product
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public string Category { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
}
This endpoint is useful for demonstrating the compression effect, but it is not representative of every production API.
For a real benchmark, use data that resembles the actual payload your application sends.
Why Large Responses Are More Interesting
Compression becomes more relevant as the response contains more compressible data.
A tiny response such as:
{"status":"ok"}
does not provide much opportunity for compression.
A response containing thousands of records is different.
For example:
Small response
|
+--> Compression overhead may dominate
Large response
|
+--> More bytes to compress
|
+--> Potentially much larger network reduction
This is why the benchmark should test multiple payload sizes rather than only one endpoint.
Configuring Compression
You can explicitly configure compression providers.
For example:
builder.Services.AddResponseCompression(options =>
{
options.EnableForHttps = true;
});
Depending on the ASP.NET Core version and packages used, additional compression providers can be configured.
The important point for benchmarking is to document exactly which compression algorithm and settings were used.
Do not compare:
Test A: gzip
Test B: Brotli
and call the difference a general "compression improvement."
Those are different algorithms with different CPU and compression characteristics.
Measuring Response Size
The first measurement is the response size.
Using curl, you can inspect a compressed request:
curl --compressed \
-o response.json \
-w "Size: %{size_download}\nTime: %{time_total}s\n" \
https://localhost:5001/api/products
You can compare this with a request that does not advertise compression support.
For example:
curl \
-H "Accept-Encoding: identity" \
-o response.json \
-w "Size: %{size_download}\nTime: %{time_total}s\n" \
https://localhost:5001/api/products
The exact result depends on the application, compression provider, server, data, and network.
Do not treat a local benchmark as a universal production result.
Measuring CPU Cost
The important part of this experiment is understanding what compression costs the server.
Suppose an endpoint takes additional CPU time to compress a large response.
Under light traffic, that may not matter.
Under heavy traffic, the same CPU cost can become significant:
Low traffic
|
v
Compression CPU cost
|
v
Usually manageable
High traffic
|
v
Many simultaneous compression operations
|
v
Higher CPU utilization
This is why a benchmark should include concurrency.
A single request does not tell you how compression behaves when many clients request large responses simultaneously.
Building a Benchmark Matrix
A useful test can compare multiple combinations.
| Test | Compression | Payload | Concurrency |
|---|
| A | Disabled | Small | Low |
| B | Enabled | Small | Low |
| C | Disabled | Large | Low |
| D | Enabled | Large | Low |
| E | Disabled | Large | High |
| F | Enabled | Large | High |
This helps answer several questions:
How much smaller is the response?
How much CPU does compression consume?
Does latency change?
Does throughput change?
Does the behavior change significantly under concurrency?
Testing Different Payload Sizes
Do not benchmark only a single large response.
Create several payload sizes.
For example:
Small -> 100 records
Medium -> 1,000 records
Large -> 5,000 records
Very Large -> 10,000 records
The exact numbers are not important.
What matters is that the test allows you to observe how compression behaves as response size increases.
The result may not scale linearly.
Using BenchmarkDotNet for Serialization
BenchmarkDotNet can be useful for isolating CPU work related to serialization and compression.
For example:
[MemoryDiagnoser]
public class JsonBenchmark
{
private readonly List<Product> products;
public JsonBenchmark()
{
products = Enumerable.Range(1, 5000)
.Select(id => new Product
{
Id = id,
Name = $"Product {id}",
Category = "Electronics",
Description =
"Sample description for benchmarking."
})
.ToList();
}
[Benchmark]
public byte[] Serialize()
{
return JsonSerializer.SerializeToUtf8Bytes(products);
}
}
This isolates JSON serialization rather than measuring the complete HTTP pipeline.
That distinction is important.
A BenchmarkDotNet test and an HTTP load test answer different questions.
BenchmarkDotNet can help isolate CPU and allocation behavior inside a process, while an HTTP test can measure the complete request path.
Measuring the Complete HTTP Pipeline
For end-to-end testing, use an HTTP load-testing tool.
A simple HttpClient loop can provide a starting point:
using var client = new HttpClient();
for (var i = 0; i < 100; i++)
{
using var response = await client.GetAsync(
"https://localhost:5001/api/products");
response.EnsureSuccessStatusCode();
await response.Content.ReadAsByteArrayAsync();
}
For serious testing, use a dedicated load-testing tool that supports:
Concurrent users
Ramp-up
Sustained load
Request rates
Percentile latency
Error rates
The benchmark should be run against a deployed or production-like environment rather than a developer laptop whenever possible.
CPU Pressure Changes the Equation
The central idea behind this benchmark is CPU pressure.
Imagine a server has plenty of unused CPU capacity.
Compression adds CPU work, but the server can absorb it.
Now increase concurrency:
10 requests
|
v
Moderate CPU
100 requests
|
v
Higher CPU
1000 requests
|
v
CPU becomes a possible bottleneck
At that point, compression can affect application throughput even though it reduces network traffic.
This does not mean compression should be disabled.
It means the decision should consider the actual bottleneck.
Network vs CPU Trade-Off
A useful way to think about compression is:
Compression Benefit
=
Network Bytes Saved
Compression Cost
=
CPU + Latency + Memory Work
If the application is network-bound, compression can be highly valuable.
If the application is already CPU-bound, aggressive compression may require more careful tuning.
The correct choice depends on the workload.
Common Mistakes
Looking Only at Response Size
A smaller response is useful, but it does not tell you the CPU cost.
Measure both network transfer and server resource usage.
Testing Only One Request
Single-request results hide concurrency effects.
Test sustained load.
Using Unrealistic JSON
Highly repetitive sample data can compress unusually well.
Use production-like data when making architecture decisions.
Ignoring Serialization
Compression happens after the application creates the response.
If JSON serialization is already the main bottleneck, compression alone will not solve the problem.
Comparing Different Payloads
Keep the underlying response data identical when comparing compression configurations.
Otherwise, the size difference cannot be attributed confidently to compression.
Troubleshooting
If compression does not appear to reduce the response size, check:
Whether the client sends Accept-Encoding.
Whether the response content type is eligible for compression.
Whether response compression middleware is registered.
Whether the selected provider is installed and configured.
Whether the response is already compressed.
Whether a proxy or CDN changes the response.
Whether the response is too small for compression to provide meaningful savings.
If CPU usage increases significantly, inspect:
Remember that the application server may not be the only component performing compression. Reverse proxies and CDNs can also participate in the delivery path.
Production Considerations
Compression decisions should be based on the entire delivery architecture.
For example:
Client
|
v
CDN / Proxy
|
v
Load Balancer
|
v
ASP.NET Core
|
v
Database
If a CDN already compresses responses, enabling another compression layer without understanding the architecture may provide little benefit.
Also consider caching.
A compressed response may be cacheable, but caches need to distinguish representations when content encoding differs. Proper HTTP cache behavior and Vary handling are therefore important.
Best Practices
Compress Large Text Responses
JSON, HTML, CSS, and JavaScript are generally more suitable compression candidates than already-compressed binary formats.
Measure CPU and Network Together
Do not optimize one resource at the expense of another without measuring the effect.
Test Under Realistic Concurrency
Compression costs become more visible when many large responses are processed simultaneously.
Use Production-Like Data
Payload structure and repetition affect compression efficiency.
Keep Compression Configuration Consistent
When comparing builds, use the same compression provider and settings.
Monitor CPU Saturation
If CPU becomes the limiting resource, investigate whether compression settings or architecture should change.
Advantages
Reduces the number of bytes transferred over the network.
Can improve response delivery for large JSON payloads.
Can be particularly useful for bandwidth-constrained clients.
Works transparently with HTTP clients that support compression.
Can reduce network-related latency for sufficiently large responses.
Disadvantages
Compression consumes CPU.
Higher compression effort can increase processing time.
Benefits are limited for small responses.
Already-compressed content generally provides less opportunity for additional compression.
High concurrency can make compression CPU costs more significant.
Compression results depend on the structure of the response data.
Conclusion
HTTP compression is not simply a question of making JSON smaller.
For large ASP.NET Core APIs, the useful question is whether the reduction in network traffic justifies the additional CPU work required to compress responses.
A good benchmark should therefore measure response size, transferred bytes, latency, CPU utilization, memory behavior, and throughput. It should also test different payload sizes and concurrency levels.
The most important lesson is to identify the actual bottleneck.
If the application is constrained by network bandwidth, compression may provide substantial value. If the server is already CPU-bound, compression settings need more careful evaluation.
Instead of choosing compression settings based on assumptions, build a controlled test with production-like JSON and realistic concurrency. The resulting measurements will give you a much better basis for deciding how aggressively your ASP.NET Core API should use HTTP compression.