Upgrading to .NET 10 LTS: Why This Is a Forced-Decision Year

If you run anything on .NET 8 or .NET 9, there’s a single date that turns “we should upgrade eventually” into “we have to upgrade this year”: November 10, 2026. On that day, both .NET 8 and .NET 9 reach end of support — simultaneously. After it, Microsoft stops shipping servicing updates, security fixes, and technical support for either version.

That simultaneity is not a typo, and it’s the crux of this series. Ordinarily you’d expect the newer release to outlive the older one. Here they converge, because of a deliberate policy change: Microsoft extended Standard Term Support from 18 to 24 months. .NET 8, a Long Term Support release from November 2023, gets its full 36 months and lands on November 10, 2026. .NET 9, a Standard Term Support release from November 2024, now gets 24 months instead of 18 — and lands on exactly the same day.

A timeline graph illustrating support periods for .NET versions 8, 9, and 10, highlighting the end of support for both .NET 8 and .NET 9 on November 10, 2026, and showing the support duration for .NET 10 until November 10, 2028.

The consequence is a genuine forcing function. It doesn’t matter which strategy you picked — the conservative shop that stayed on .NET 8 LTS and the fast-moving shop that jumped to .NET 9 are now in exactly the same boat, with the same deadline and the same target: .NET 10. Every .NET application in your portfolio needs to be on .NET 10 by November 2026, and the runway is shorter than it looks once you account for testing, third-party dependencies, and change-freeze windows.

Why “unsupported” is a real problem, not a nag

It’s tempting to treat end-of-support as a soft recommendation. It isn’t, for three concrete reasons:

  • No more security patches. After November 10, 2026, a newly disclosed CVE in the runtime or base libraries simply won’t be fixed for .NET 8 or .NET 9. You’d be running a known-vulnerable runtime with no official remedy — Microsoft’s own guidance is blunt that using out-of-support software puts your applications, data, and environment at risk.
  • Compliance failure. For regulated shops — financial services, healthcare, government contractors — running an unsupported runtime is often an automatic audit finding. “We’re planning to upgrade” doesn’t satisfy an auditor looking at a production system on an EOL framework.
  • Emergency-patching spiral. Teams that miss the deadline don’t escape the work; they just do it later, under pressure, during an incident. That’s the most expensive possible time to migrate.

There’s one release-valve worth knowing about: third-party extended-support vendors will sell you post-EOL security patches for .NET 8/9 to buy time for a genuinely un-migratable app. Treat that as a bridge for the exceptional case, not a strategy — it’s a way to stop the security clock on a mission-critical system while you finish the real migration, not a substitute for it.

How the cadence works — so this never surprises you again

Understanding the release model turns the deadline from a shock into a predictable planning input. .NET ships a new major version every November, and the parity of the version number tells you the support length:

Infographic illustrating the .NET release cadence with release dates and support durations for .NET 8 to .NET 12, highlighting LTS and STS versions.

Even-numbered releases are LTS (three years of support): .NET 8, .NET 10, .NET 12. Odd-numbered releases are STS (now two years): .NET 9, .NET 11. Crucially, Microsoft is explicit that the quality of LTS and STS releases is identical — the only difference is the length of the free support window. So the strategic question isn’t “is LTS more stable?” (it isn’t) but “how often do I want to be forced to upgrade?” For most enterprise shops the answer is LTS-to-LTS — .NET 8 → .NET 10 → .NET 12 — which minimises forced migrations and gives you the longest predictable runway each time. That’s exactly the move on the table now.

Beyond avoiding the cliff: .NET 10 earns the upgrade

Here’s the good news that makes this less of a chore: even if there were no deadline, .NET 10 would be worth moving to. It’s one of the largest .NET releases ever, and — unusually — a big chunk of the value lands for free the moment you retarget and rebuild.

The headline is that the runtime got materially faster without any code changes on your part. .NET 10 brings improvements to JIT inlining, method devirtualisation, stack allocation, and loop optimisation, plus hardware acceleration (AVX10.2 on Intel, Arm64 SVE) and write-barrier improvements that cut GC pause times by roughly 8–20%. These apply automatically at startup — you re-target, rebuild, run your existing benchmarks, and the numbers improve. In an era where every team is watching cloud spend and energy (the subject of the green-coding-and-cost series in this library), a faster runtime is a direct infrastructure-cost reduction you get for the price of an upgrade.

What’s New

.NET 10 bundles language, runtime, security, AI, web, and data improvements into a single release. Rather than an exhaustive changelog, here’s the map that matters for an upgrade decision — the things that change how you build.

Overview of new features in .NET 10, including updates in language, runtime, AI, security, web, data, and orchestration.

C# 14: productivity, not paradigm shift

C# 14 is a refinement release — no new mental model, just less friction. The standouts:

  • Field-backed properties via the field contextual keyword. This closes a long-standing annoyance: the moment you needed any custom logic in an accessor, you had to hand-write a backing field. Now the compiler generates it and you reference it as field:
// C# 14: custom logic with no explicit backing field
public string Name
{
get => field;
set => field = value?.Trim() ?? ""; // 'field' is the compiler-generated backing store
}
  • Extension members — the biggest addition. Extension blocks now support static extension methods and both static and instance extension properties, not just the instance methods C# has always had. It makes extending types you don’t own far more natural.
  • Null-conditional assignment: customer?.Address = newAddress; assigns only if customer is non-null — the mirror image of the null-conditional read you already use.
  • File-based apps: a single .cs file you run directly with dotnet run, with top-of-file directives for SDK and package references — and, new in .NET 10, they support dotnet publish and even NativeAOT. It collapses the distance between “script” and “app” and is a genuinely nice on-ramp for tooling and newcomers.

One practical note: existing projects compile unchanged, but to use new C# 14 syntax you set <LangVersion> to 14 (it’s the default with the .NET 10 SDK, but worth knowing when multi-targeting).

Runtime and NativeAOT: the free performance

As previously stressed, the runtime improvements are the ones you get without writing code — JIT inlining, method devirtualisation, better stack allocation and loop optimisation, AVX10.2 and Arm64 SVE acceleration, and GC pause reductions of 8–20%. Re-target, rebuild, re-run your benchmarks, and hot paths get cheaper. NativeAOT also advances: smaller, faster ahead-of-time-compiled binaries with improved struct-argument codegen — attractive for startup-sensitive workloads (serverless, CLIs, containers) where you want no JIT warm-up and a small footprint. The one caveat: NativeAOT trims aggressively, so re-run your trimming analysis if you rely on reflection.

The native AI story: the Microsoft Agent Framework

This is the headline feature and the reason “AI” now belongs in a .NET release note. Historically, adding AI to a .NET app meant bolting on external SDKs. .NET 10 brings a first-class, layered AI stack, anchored by the Microsoft Agent Framework (MAF), which reached 1.0 GA in April 2026 — a production-ready, open-source SDK for building agents and multi-agent workflows that unifies Semantic Kernel’s enterprise foundations with AutoGen’s orchestration.

Diagram illustrating the native AI stack in .NET 10, featuring sections on connectivity, Microsoft Agent Framework 1.0, Microsoft.Extensions.AI, and enterprise plumbing.

The layering is the point, and it maps directly onto the agent patterns from earlier series in this library:

  • Microsoft.Extensions.AI is the vendor-neutral abstraction layer — chat, embeddings, and tool-calling behind interfaces, so you can swap model providers without rewriting your app. This is the “don’t hard-code your model” discipline made into a framework.
  • MAF builds agents and multi-agent orchestration on top, with pluggable memory, middleware pipelines, and enterprise plumbing (OpenTelemetry observability, DefaultAzureCredential, Azure AI Foundry integration, a browser-based DevUI debugger).
  • MCP support is GA — your agents dynamically discover and invoke external tools over Model Context Protocol servers instead of hand-writing an HTTP wrapper per API. That’s the exact protocol the MCP series in this library covered, now native in .NET. A2A (agent-to-agent, cross-runtime) is in preview.

Two things worth flagging for the security-minded: MAF ships with guardrails that echo the agent-security series — server-initiated MCP sampling is denied by default, and file-access tools require approval — and because these APIs are young, they still see breaking changes between versions. And on the data side, EF Core 10 adds vector search for SQL Server 2025 and Azure SQL, so embeddings and similarity queries run in the database — the retrieval layer from the RAG series, now first-class in your ORM.

Post-quantum cryptography: prepare now, adopt deliberately

.NET 10 is, by Microsoft’s own description, the most significant cryptography release in years, because it brings post-quantum algorithms into the base libraries. The two that matter: ML-KEM (FIPS 203, key encapsulation) and ML-DSA (FIPS 204, digital signatures), with simplified APIs and Windows CNG backend acceleration (a cross-platform managed implementation covers Linux and macOS):

// ML-DSA (FIPS 204) post-quantum signatures — simplified .NET 10 API
using var key = MLDsa.GenerateKey(MLDsaAlgorithm.MLDsa65);
byte[] signature = key.SignData(dataToSign);
bool valid = key.VerifyData(dataToSign, signature);

The most important primitive for real migrations is Composite ML-DSA, which combines a post-quantum algorithm with a classical one in a single signature — the hybrid pattern standards bodies recommend, so you gain quantum resistance without betting everything on newer algorithms. This connects to the digital-provenance and confidential-computing series: signing and attestation are exactly where “harvest now, decrypt later” threats bite, and financial, government, and healthcare shops are already being asked for quantum-resistant roadmaps.

The honest framing, which I’ll reinforce later: PQC support is preparatory. Having the primitives in the box lets you plan, prototype, and satisfy a compliance checkbox — but adopting them in production requires interoperability testing and threat modelling. It is not a flip-the-switch change, and you shouldn’t rip out working crypto to chase it before your partners and platforms are ready.

Web, data, and orchestration

Rounding out the release: ASP.NET Core 10 focuses on Blazor (WebAssembly preloading for faster perceived load, automatic memory-pool eviction to stop long-lived servers accumulating stale memory), OpenAPI v2, and Identity. EF Core 10 adds named query filters — finally allowing multiple global filters per entity (the long-standing soft-delete-plus-multi-tenancy pain) that you can selectively disable per query. Aspire 13 advances code-first orchestration for microservices and containers, with an aspire update command that scans your AppHost and packages, validates compatibility, and upgrades safely. And JSON serialisation gains strictness options — disallowing duplicate properties, stricter settings, PipeReader support — that quietly remove distributed-systems footguns.

The Migration

The good news up front: a .NET 8-or-9 to .NET 10 upgrade is, for most well-maintained applications, a retarget-and-fix exercise, not a rewrite. (Migrating from .NET Framework 4.x is a genuinely different and larger effort — a separate project involving extracting logic into .NET Standard libraries and replacing System.Web/WCF — so this I assume you’re already on modern .NET.) The workflow is well-trodden and increasingly tool-assisted.

A flowchart demonstrating the .NET 10 migration workflow, outlining six key steps: Assess, Retarget, Update packages, Fix breaks, Test, and Ship. The bottom section mentions tooling support including the .NET Upgrade Assistant and AI-powered tools.

The workflow, step by step

1. Assess. Inventory every app, its target framework, and its dependencies. The critical gate here is third-party package compatibility — a NuGet dependency without a .NET 10 build will block you. List what’s outdated before you touch anything:

dotnet list package --outdated        # see what needs bumping
dotnet list package --vulnerable # and what's a security liability today

2. Retarget. Change the Target Framework Moniker. During a phased rollout, multi-targeting keeps the old target building while you validate the new one:

<!-- straight retarget -->
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
</PropertyGroup>

<!-- or multi-target during the transition (recommended for libraries) -->
<PropertyGroup>
<TargetFrameworks>net9.0;net10.0</TargetFrameworks>
</PropertyGroup>

3. Update packages. Bump every Microsoft.Extensions.*, Microsoft.AspNetCore.*, and Microsoft.EntityFrameworkCore.* reference to 10.0.x (or update Directory.Packages.props if you use Central Package Management), then dotnet restore. Watch for a few .NET-10-specific NuGet behaviors: NU1510 flags a package you can now delete because it moved into the shared framework; a PackageReference with no Version is now an error; and restore audits transitive dependencies, so you may see new vulnerability warnings worth heeding.

4. Fix breaking changes. .NET categorises these as source-incompatible (won’t compile), binary-incompatible (won’t bind), and behavioural (compiles and runs, but acts differently — the dangerous kind). Common ones on this hop: obsoletions SYSLIB0058SYSLIB0062, System.Linq.Async superseded by the built-in IAsyncEnumerable, OpenAPI v2 API changes, some cryptography renames, and — in containers — a Debian-to-Ubuntu base-image change in the official .NET images. The single best defence is to fail loudly:

<PropertyGroup>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> <!-- obsolete-API warnings become build failures you must address -->
</PropertyGroup>

5. Test and benchmark. Run your full suite, and — because it’s free money — benchmark the .NET 10 build against your .NET 9 baseline with BenchmarkDotNet. The runtime gains from previous apply automatically, so this step both validates the upgrade and quantifies the performance win for your stakeholders.

6. Deploy. Update CI images, build agents, and Dockerfiles to the .NET 10 SDK, then ship.

Let the tooling carry the load

Two tools make this dramatically less manual, and using them is the norm in 2026, not a shortcut.

The .NET Upgrade Assistant (dotnet tool install -g upgrade-assistant, then upgrade-assistant upgrade MyProject.csproj) analyzes the project, updates the TFM and package versions, applies known fixes, and generates a report. It runs in-place, side-by-side, or incremental mode.

The bigger leap is the AI-powered modernize-dotnet agent for GitHub Copilot, which follows an assess → plan → execute model: it analyzes dependencies and configuration, generates a migration plan flagging specific breaking changes, then executes the transformations — updating package versions, fixing namespace changes, resolving compiler errors. Critically, it creates a Git commit at each stage, so you can review diffs, roll back individual steps, or cherry-pick, and it runs identically from VS Code, the Copilot CLI, or a GitHub pull request. This is the “AI participates in structured engineering workflows” pattern from the agentic-engineering series, pointed squarely at migration drudgery — with a human reviewing every commit rather than a black-box rewrite.

A pre-migration hygiene tip that pays off: if you’re still on Newtonsoft.Json, consider moving to System.Text.Json first, as a separate mechanical change — it’s faster, avoids a class of deserialisation vulnerabilities, and removes a variable from the main upgrade.

The honest gotchas

An upgrade this broad has real edges. None are blockers; all deserve planning.

An infographic detailing five key challenges related to modern .NET development, including topics on breaking changes, package lag, AOT compatibility, post-quantum crypto preparation, and timing considerations.
  • Behavioural breaking changes are the sneaky ones. Source and binary breaks announce themselves at build time; behavioural changes compile cleanly and misbehave at runtime. Your test suite and warnings-as-errors are the safety net.
  • Third-party lag can gate you. With 478,000+ NuGet packages, not all update on day one. One un-migrated critical dependency blocks the whole app — which is why compatibility assessment comes first, not after you’ve retargeted.
  • NativeAOT isn’t universal. Its aggressive trimming breaks reflection-heavy and dynamic-code patterns. If you adopt it, re-run trimming analysis; if your app leans on reflection, it may not be a fit — and that’s fine, AOT is opt-in.
  • PQC is a roadmap item, not a rebuild. As I’ve previously said: the primitives are in the box for planning and prototyping, but production adoption needs interop testing and threat modelling. Don’t destabilise working cryptography to chase quantum-resistance before your ecosystem is ready.
  • Timing cuts both ways. Don’t wait until November 2026 — testing, dependency chases, and change-freeze windows consume the runway fast. But don’t carelessly rush a mission-critical system either. Pilot a representative non-critical service first, profile it, learn the breaking changes on low stakes, then roll out.

The adoption playbook

  1. Inventory now. List every app, its TFM, and its dependencies; the runway to November 2026 is shorter than it looks.
  2. Assess third-party compatibility first — one blocking package can stall the whole plan, so find it early.
  3. Pilot a non-critical service end to end to learn the breaking changes cheaply.
  4. Lean on the tooling — Upgrade Assistant for the mechanics, the modernize-dotnet Copilot agent for AI-assisted, commit-by-commit migration you can review.
  5. Retarget, update packages, build with warnings-as-errors, and work the breaking-change list.
  6. Test hard and benchmark against your .NET 9 baseline to validate correctness and capture the free performance win.
  7. Sequence LTS-shaped work deliberately — adopt the runtime gains and framework updates now; treat PQC and NativeAOT as opt-in roadmap items where they fit.
  8. Update CI, containers, and DevOps tooling to the .NET 10 SDK as part of the rollout, not after.
  9. Keep a bridge only for true exceptions — third-party extended support for a genuinely un-migratable app, never as the plan.

The whole picture

Step back and there is one clear decision. .NET 8 and .NET 9 both hit end of support on November 10, 2026, which makes moving to .NET 10 LTS (supported to November 2028) a forced decision this year, not a someday-maybe. But it’s a forced decision worth making, because .NET 10 is one of the largest releases ever — free runtime performance, C# 14, a native AI stack in the Microsoft Agent Framework, post-quantum cryptography, and updates across web, data, and orchestration. And the migration itself is a tractable, well-tooled retarget-and-fix for modern .NET apps, provided you assess dependencies early, pilot first, lean on the Upgrade Assistant and the AI modernisation agent, and respect the honest gotchas.

Leave a Reply