Performance improvements in a new .NET release are interesting, but they become meaningful only when they improve an application that people actually use. A runtime benchmark might show fewer allocations or faster execution for a particular operation. That does not automatically mean every ASP.NET Core API will become faster after upgrading.
The better approach is to take an existing API, run it under .NET 10 and .NET 11, and measure the difference under the same conditions.
.NET 11 introduces several improvements that can affect web applications. These include changes to Kestrel request processing, reduced allocations in HTTP logging, Zstandard compression support, and improvements to HTTP/3 request handling. The runtime also includes work on asynchronous execution and JIT optimization.
In this article, we will build a small ASP.NET Core API, prepare it for performance testing, and examine how to measure these improvements without relying on assumptions.
1. Understand Which .NET 11 Improvements Matter to an API
Before changing application code, understand where the improvements occur.
Improvement | What changes | Where it can help |
|---|
Kestrel request parsing | More efficient handling of malformed HTTP/1.1 requests | Services exposed to scanning, malformed traffic, or misconfigured clients |
HTTP logging | Reuses response buffering streams | Applications that enable response-body logging or logging interceptors |
Zstandard compression | Adds Zstandard response compression and request decompression | APIs transferring larger JSON or other compressible payloads |
HTTP/3 processing | Starts processing requests earlier on new connections | Applications using HTTP/3, particularly for initial request latency |
Runtime Async | Changes how asynchronous methods are executed | Applications with suitable async workloads that opt into the preview feature |
One important distinction is that not all these improvements affect normal API requests in the same way.
For example, Kestrel's non-throwing parsing path targets malformed HTTP/1.1 requests. Microsoft reports throughput improvements of up to 20–40% in scenarios involving many malformed requests. This should not be interpreted as a 20–40% improvement for normal API traffic.
Similarly, HTTP logging improvements matter when the application actually uses the relevant logging features.
The first step is to identify which feature matches the workload you want to improve.
2. Create a Small ASP.NET Core API
Let's create a simple API that returns order information. This gives us a consistent endpoint to test across runtime versions.
Create the project:
dotnet new webapi -n PerformanceDemo
cd PerformanceDemo
For a minimal API example, replace the contents of Program.cs with:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/api/orders", () =>
{
var orders = Enumerable.Range(1, 1000)
.Select(id => new
{
OrderId = id,
CustomerName = $"Customer {id}",
ProductName = $"Product {id % 20}",
Quantity = id % 5 + 1,
Amount = (id % 100) * 12.50m,
Status = id % 2 == 0 ? "Completed" : "Pending"
});
return Results.Ok(orders);
});
app.Run();
This endpoint generates 1,000 order records and returns them as JSON.
It is intentionally simple. We want to observe the behavior of the web server, serialization, and response handling without introducing database queries or external service calls.
For a production comparison, you should eventually test an endpoint that represents your real workload, including its database and application logic.
3. Run the Same Application on .NET 10 and .NET 11
The most important part of a runtime comparison is keeping the application code consistent.
You can use a multi-targeted project to build the same source against both frameworks.
Update the project file:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFrameworks>net10.0;net11.0</TargetFrameworks>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
</Project>
Make sure the corresponding SDKs and runtimes are installed on your machine.
Check the installed SDKs:
dotnet --list-sdks
Check the installed runtimes:
dotnet --list-runtimes
Build each target:
dotnet build -c Release -f net10.0
dotnet build -c Release -f net11.0
Run the application using the .NET 10 target:
dotnet run -c Release -f net10.0 --urls http://localhost:5000
Run the .NET 11 target separately:
dotnet run -c Release -f net11.0 --urls http://localhost:5000
Use the same machine, application configuration, endpoint, and test data for both runs.
The .NET 11 release documentation currently describes the release candidate. Verify the release status and SDK version before using this setup in a production migration.
4. Test Zstandard Response Compression
One useful improvement for APIs that return larger payloads is Zstandard compression.
Compression reduces the number of bytes transferred between the server and client. This can be particularly helpful when an API returns large JSON responses over networks with limited bandwidth.
.NET 11 adds Zstandard support to ASP.NET Core's existing response-compression and request-decompression middleware.
Update Program.cs:
using Microsoft.AspNetCore.ResponseCompression;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddResponseCompression();
builder.Services.AddRequestDecompression();
var app = builder.Build();
app.UseRequestDecompression();
app.UseResponseCompression();
app.MapGet("/api/orders", () =>
{
var orders = Enumerable.Range(1, 1000)
.Select(id => new
{
OrderId = id,
CustomerName = $"Customer {id}",
ProductName = $"Product {id % 20}",
Quantity = id % 5 + 1,
Amount = (id % 100) * 12.50m,
Status = id % 2 == 0 ? "Completed" : "Pending"
});
return Results.Ok(orders);
});
app.Run();
The middleware handles compression negotiation with clients that support the relevant content encoding.
You can inspect the response headers using curl:
curl -i \
-H "Accept-Encoding: zstd" \
http://localhost:5000/api/orders
Look for the Content-Encoding response header. When Zstandard is negotiated and the response is eligible for compression, the header identifies the encoding.
To compare compression effectively, record:
Response size before compression.
Response size after compression.
Server response time.
CPU utilization during the test.
Network transfer time.
Compression is not automatically beneficial for every endpoint. Small responses may not justify the additional processing, while already-compressed files may gain very little.
Also, review your application's security requirements before enabling response compression over HTTPS. Compression can introduce information-leakage risks when secrets and attacker-controlled content are compressed together.
5. Understand the HTTP Logging Improvement
HTTP logging is useful when investigating production problems. It helps developers understand incoming requests and outgoing responses.
However, logging response bodies can introduce additional buffering and allocation overhead.
.NET 11 improves the HTTP logging middleware by pooling its ResponseBufferingStream instances. This reduces per-request allocations when response-body logging or logging interceptors are enabled.
You can enable HTTP logging in the application:
using Microsoft.AspNetCore.HttpLogging;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpLogging(options =>
{
options.LoggingFields =
HttpLoggingFields.RequestPropertiesAndHeaders |
HttpLoggingFields.ResponsePropertiesAndHeaders;
});
var app = builder.Build();
app.UseHttpLogging();
app.MapGet("/api/orders", () =>
{
return Results.Ok(new
{
Message = "Orders retrieved successfully"
});
});
app.Run();
This example logs request and response properties and headers. It does not enable response-body logging.
To investigate the pooling improvement, test a separate configuration that enables response-body logging and compare allocations under repeated requests. Keep that test isolated from the normal API benchmark.
Do not enable body logging indiscriminately in production. It can expose sensitive information, increase log volume, and add processing overhead even when the middleware is more efficient.
6. Measure API Performance with a Load Test
Now we need a repeatable way to send requests and compare results.
A tool such as k6 can generate concurrent traffic and report response-time statistics.
Create a file named performance-test.js:
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
vus: 50,
duration: '60s',
};
export default function () {
const response = http.get(
'http://localhost:5000/api/orders'
);
check(response, {
'status is 200': (r) => r.status === 200,
});
sleep(1);
}
Run the test:
k6 run performance-test.js
Repeat the same test against the .NET 10 and .NET 11 builds.
For a fair comparison, collect the following metrics:
Metric | What it tells you |
|---|
Average response time | General request latency |
p95 response time | How long the slower 5% of requests take |
p99 response time | How long the slowest 1% of requests take |
Requests per second | How much traffic the API handles |
Failed requests | Whether the application remains reliable |
CPU utilization | How much processor capacity the workload consumes |
Memory and allocations | Whether the application creates unnecessary memory pressure |
Do not compare only average response time. An API can have a good average while still producing poor response times for a significant number of users.
Run a warm-up period before collecting measurements. Repeat the test several times, use the same concurrency and duration, and avoid running unrelated workloads on the test machine.
Also, test compression both enabled and disabled. Otherwise, you may accidentally attribute a compression-related improvement to the runtime itself.
7. What About Runtime Async?
.NET 11 also includes Runtime Async, an approach that moves parts of asynchronous method execution from compiler-generated state machines to runtime support.
It aims to reduce overhead and generated code size in suitable scenarios.
Runtime Async is still documented as a preview feature. To experiment with it in a .NET 11 project, add the following property:
<PropertyGroup>
<TargetFramework>net11.0</TargetFramework>
<Features>runtime-async=on</Features>
</PropertyGroup>
This is an experiment worth conducting for services that make extensive use of asynchronous operations.
However, do not enable it in a production service simply because it is a new runtime feature. Test the application's actual asynchronous workload, including cancellation, exception handling, request throughput, and latency.
It is also important to understand that an API waiting for a slow database query will not become fast simply because its async implementation has less overhead. The database operation may still be the dominant bottleneck.
8. Common Mistakes During Performance Testing
A runtime comparison can produce misleading results if the testing process is not controlled.
Some mistakes to avoid include:
Comparing Debug builds instead of Release builds.
Running the .NET 10 and .NET 11 tests with different application configurations.
Testing only one request instead of sustained traffic.
Ignoring database and external-service response times.
Comparing compressed responses with uncompressed responses.
Treating a microbenchmark result as an application-level performance guarantee.
Ignoring memory usage and CPU consumption.
Running tests without warming up the application.
Testing a preview runtime without documenting its status.
The objective is not to prove that one runtime is always faster. It is to identify whether the runtime changes improve the workload that matters to your application.
Summary
.NET 11 introduces several performance improvements that are relevant to ASP.NET Core applications. Kestrel has a more efficient path for malformed HTTP/1.1 requests, HTTP logging reduces allocations in certain configurations, Zstandard adds another compression option, and HTTP/3 can begin processing requests earlier on new connections.
The best way to evaluate these changes is to run the same API on .NET 10 and .NET 11, use identical test conditions, and measure latency, throughput, allocations, CPU utilization, and failures.
Do not upgrade based on a benchmark headline alone. Test the endpoints that matter to your users, investigate the results, and verify that the improvement remains consistent under realistic production workloads.
Join the conversation! Your thoughts help the community grow.