If your services run on .NET 10, you will soon be asked the same question again: do we move to .NET 11?
The .NET team has now released .NET 11 Release Candidate 2 (RC2). The important part is the licence that comes with it: RC2 has a go-live support licence, which means Microsoft supports it for production use. General availability is expected in November 2026. The team announced the release again on X on 22 October, and the full notes are in the official .NET blog post.
This post lists what actually changed from .NET 10 to .NET 11, grouped by the part of the stack you touch every day, and ends with a short plan for upgrading without surprises. Everything below comes from the official release notes and the Microsoft Learn "What's new" pages for RC2. A few features are still marked experimental, and I say so when that is the case.
At a glance
| Area | What is new in .NET 11 |
|---|---|
| Runtime | Runtime Async (the runtime handles await), JIT and NativeAOT improvements, higher minimum CPU requirements |
| C# 15 | Union types, closed hierarchies, collection expression arguments, extension indexers, labeled break and continue |
| Libraries | Zstandard compression, new Process APIs, IEEE decimal types, JSON union support, LINQ FullJoin |
| ASP.NET Core | OpenAPI 3.2, async validation, SignalR token refresh, built-in OpenTelemetry tracing, Blazor additions |
| EF Core 11 | Smaller SQL for joins, FullJoin, MaxBy and MinBy, vector indexes, better migrations |
| SDK | NativeAOT CLI on by default, richer dotnet test, file-based app improvements, container publishing |
The runtime: Runtime Async
This is the change with the widest reach, because it touches every async method you have ever written.
Until now the C# compiler rewrote each async method into a state machine: a generated class that stores your local variables and a MoveNext() method that resumes the work after each await. It works well, but it adds overhead and it is the reason async stack traces are full of generated names.
In .NET 11 the runtime itself takes over suspending and resuming async methods. The Learn page describes the result as cleaner stack traces and lower overhead. For projects that target net11.0, Runtime Async no longer needs <EnablePreviewFeatures>true</EnablePreviewFeatures>, and the runtime libraries are compiled with runtime-async=on.
You do not change your await calls. The same code gets the new behaviour when you retarget. The team also added follow-up work: JIT compilation of a dedicated runtime-async version of synchronous task-returning methods, async continuations that skip ExecutionContext capture when no ambient state is in use, and tiered compilation for runtime-async methods.
Other runtime changes worth knowing:
- JIT improvements: better bounds-check elimination, devirtualization, and constant folding, plus new Arm SVE2 intrinsics.
- NativeAOT: faster interface dispatch through a shared helper, which reduces binary size at call sites.
- Crash reports: an in-process crash report that captures the managed stack trace and runtime state before the process exits, on Linux, macOS and mobile.
- Higher minimum hardware: .NET 11 requires more modern instruction sets on x86/x64 and Arm64. Check your oldest servers and build agents before you retarget. This is the item most likely to surprise an operations team.
C# 15
C# 15 is the default language version for projects that target .NET 11. These are the features you will notice.
Union types
A union represents a value that is exactly one of several case types. You declare it with the new union keyword:
public record class Cat(string Name);
public record class Dog(string Name);
public record class Bird(string Name);
public union Pet(Cat, Dog, Bird);
Pet pet = new Dog("Rex");
string name = pet switch
{
Dog d => d.Name,
Cat c => c.Name,
Bird b => b.Name,
};
Each case type converts to Pet implicitly, and the compiler checks that the switch is exhaustive. If you add a fourth case later, every switch that forgets it gets a warning. That replaces a lot of "common base class plus a default that throws" code, and it is the closest C# has come to the sum types you may know from F#, Rust or TypeScript.
The runtime ships UnionAttribute and IUnion, and System.Text.Json gains support for C# and F# unions. Some parts of the original proposal are still not implemented, so treat unions as a feature to adopt carefully in new code first.
Closed hierarchies
The closed modifier means a class can only be derived from inside its own assembly. The compiler then knows every direct subclass, so a switch over them is exhaustive without a default arm:
public closed record class GateState;
public record class Closed : GateState;
public record class Open(float Percent) : GateState;
string Describe(GateState state) => state switch
{
Closed => "closed",
Open(var percent) => $"{percent}% open",
};
The smaller features
- Collection expression arguments: pass constructor arguments with
with(...), for exampleList<string> names = [with(capacity: 20), .. values];or aHashSet<string>with a case-insensitive comparer. - Extension indexers: declare an indexer inside an
extensionblock, sonumbers[2]works on anIEnumerable<int>. - Labeled
breakandcontinue: exit or continue an outer loop by name, instead of a flag variable or agoto. - Memory safety work: the first step of a longer effort. Declaring pointers and using
fixedno longer needs anunsafecontext in preview; reading through a pointer still does. There is also a newunsafe(expression)form.
Libraries
The base class library adds many small APIs. These are the ones that matter for backend work:
- Zstandard compression in
System.IO.Compression, with CRC32 validation when reading ZIP entries. - Process APIs: run-and-capture helpers, fire-and-forget launches,
ProcessStartInfo.StartSuspended, andProcess.Signal(PosixSignal). - IEEE decimal types:
Decimal32,Decimal64andDecimal128, plus a genericComplex<T>. - System.Text.Json: union support, JSON Lines (JSONL) output, and expanded polymorphism and source generation.
- LINQ:
FullJoinand tuple-returningJoinandGroupJoinoverloads, acrossEnumerable,QueryableandAsyncEnumerable. - Async validation:
AsyncValidationAttribute,IAsyncValidatableObjectandValidator.ValidateObjectAsync. - Observability: built-in OpenTelemetry metrics for
MemoryCache. - Networking and TLS: typed DNS record lookups through
DnsResolver, configurable HTTP connection eviction,SocketsHttpHandler.InitialHttp2MaxConcurrentStreams, and TLS handshake hardening. - Security APIs (experimental): composite ML-KEM and HPKE for post-quantum work.
ASP.NET Core 11
For API developers the changes are practical rather than flashy.
- OpenAPI 3.2 by default. Generated documents now target OpenAPI 3.2, and Server-Sent Events endpoints that return
SseItem<T>are described with the newitemSchema. The change comes with breaking changes from the underlyingMicrosoft.OpenApilibrary. If a client generator or gateway in your pipeline does not support 3.2 yet, set the version back explicitly:
builder.Services.AddOpenApi(options =>
{
options.OpenApiVersion = Microsoft.OpenApi.OpenApiSpecVersion.OpenApi3_1;
});
- Async validation in minimal APIs. Validators can now be asynchronous end to end, and validation messages can be localised.
ValidatableTypeAttributeandSkipValidationAttributeare no longer experimental. - SignalR authentication refresh. A connection can refresh its token without dropping, so a hub connection no longer closes when its bearer token expires. Blazor Server circuits pick up the refreshed identity too.
- Built-in OpenTelemetry tracing. ASP.NET Core adds the OpenTelemetry semantic-convention attributes to the HTTP server activity itself. Subscribe to the
Microsoft.AspNetCoreactivity source; the separateOpenTelemetry.Instrumentation.AspNetCorepackage is no longer needed for this. - Zstandard for response compression and request decompression, enabled by default.
- Faster Kestrel paths: the HTTP/1.1 parser handles malformed requests without throwing an exception each time, and HTTP/3 starts processing requests earlier.
- Identity and security:
TimeProvidersupport (so token expiry and lockouts can be tested), friendlier passkey names, and an experimental Device Bound Session Credentials package that ties a session to a key held by the browser. - MCP server template now ships in the .NET SDK:
dotnet new mcpserver.
On the Blazor side there are many additions: QuickGrid pagination and sorting changes (virtualization APIs are no longer experimental), a CacheView component for static server rendering, automatic circuit pause when a tab is hidden, a Web Worker template, smaller WebAssembly output, and experimental AI components for agent-style interfaces.
EF Core 11
EF Core 11 gives you speed without code changes, and a few new query features.
Smaller SQL for joins. In split queries, EF used to join reference navigations that the collection query did not need. It also added redundant keys to ORDER BY. EF Core 11 prunes both.
The EF team reports a 29% improvement for a simple, common split-query scenario. That is their benchmark, so measure your own queries before you quote a number.
Other EF Core 11 changes:
- LINQ: translation for the new
FullJoinoperator,MaxByandMinBy, and improvedGroupBytranslation. - Cleaner SQL: no-op
CASTexpressions are removed, which lets the database use an index it could not use before. - JSON:
EF.Functions.JsonPathExists()andJsonContains(), plus JSON indexes on SQL Server. - Vector search: vector indexes and
VECTOR_SEARCH()for SQL Server 2025 (experimental), plus full-text and hybrid search improvements. - Migrations:
dotnet ef database update <name> --addcreates and applies a migration in one step, the model snapshot now records the latest migration ID so diverging branches produce a merge conflict instead of a silent problem, andExcludeForeignKeyFromMigrations()keeps a relationship in the model without creating the database constraint. - Tooling: default options can live in a
.config/dotnet-ef.jsonfile.
SDK and tooling
Developer-loop changes are easy to overlook, and they save the most time across a team:
- NativeAOT CLI and MSBuild server on by default, so
dotnetcommands start faster. dotnet test:--no-dependencies,--use-current-runtime, per-assembly counts, live display of running tests, and templates for xUnit v3 (on Microsoft.Testing.Platform) and NUnit.- File-based apps:
#:includeto split a script across files, and Native AOT build output reuse. - Containers: multi-architecture images with Podman, reproducible digests through
SOURCE_DATE_EPOCH, and skipped uploads for layers the registry already has. - NuGet: a minimum package publication age, set from the CLI with
--min-publish-age-hours. It is a cheap defence against a freshly published malicious package. - Smaller wins:
dotnet slncan edit solution filters (.slnf),dotnet run -epasses environment variables, anddotnet watchrecovers from crashes.
MAUI, Windows Forms and F# also get updates in this release. MAUI adds C# UI authoring and Apple Icon Composer assets, WinForms adds visual-style and accessibility improvements, and F# gains runtime async support and Native AOT improvements.
Should you upgrade, and when?
My advice for a backend team:
- Start testing on RC2 now. Install the .NET 11 SDK, retarget a branch to
net11.0, and fix the build. Go-live support means you can run a pilot, but a release candidate is not the final build, so keep the pilot small. - Read the breaking changes first. The three I would check: the higher minimum CPU requirements, the OpenAPI 3.2 default, and the
QuickGridCSS change if you use Blazor. - Pilot one low-risk service. Compare latency, memory and cold-start time against the same service on .NET 10. Runtime Async and the EF Core SQL changes are the two things most likely to show up in a graph.
- Roll out after general availability. Move services one at a time and keep a rollback.
- Adopt new language features slowly. Union types and closed hierarchies are useful, but they change how code is modelled. Use them in new code before you convert old code.
If you are on .NET 10 today you are not in a hurry. Use the time to test, so that the upgrade is a decision you make, not an emergency.
Sources
Working on something like this?
I take on backend, payments and deployment projects. I reply within one working day.