.NET 8 reaches end of support on November 10, 2026: upgrading to .NET 10
.NET 8 and .NET 9 both reach end of support on November 10, 2026, and .NET 10 is where they go next. This is the upgrade in the order it should be done: what to inventory first, the mechanical change, the breaking changes between 8 and 10 that actually bite, and how to release it so it can be taken back.
10 min read · Updated
01What actually happens on November 10
.NET 8, the long-term support release from November 2023, and .NET 9, a standard-term release that now gets 24 months of support, both reach end of support on the same day: November 10, 2026. After that date Microsoft ships no more servicing updates, no security fixes and no technical support for either.
Nothing stops working on the 11th. Applications built on .NET 8 or .NET 9 carry on running exactly as before. What changes is that any vulnerability found in the runtime after that date stays unpatched in every application still on it, and the gap widens with each Patch Tuesday that passes.
November 10 is itself a Patch Tuesday, so each version may receive one final update that day if there is a known critical issue. Microsoft's recommended destination is .NET 10, a long-term support release supported through November 2028.
The deadline is about the runtime your application runs on in production, not the SDK on a developer's machine. A .NET 8 application on a fully patched server is still an unsupported application after the 10th.
02Go to .NET 10, not .NET 9
.NET 9 ends on the same day as .NET 8, so it is not a stepping stone worth stopping on. Move straight to .NET 10.
Skipping a version does not skip its changes, though. An application going from 8 to 10 picks up the breaking changes introduced in .NET 9 as well as those in .NET 10, and the same holds for ASP.NET Core and Entity Framework Core. Microsoft publishes a separate list for each version; read both.
03Take inventory before changing a line
Changing the target framework is the smallest part of the work. The part that gets missed is everything around the code that also names a .NET version, so list it first.
- Every project's TargetFramework, including class libraries, test projects and small internal tools, and any Directory.Build.props that sets it centrally
- global.json, if the repository pins an SDK version
- Dockerfiles and the base images they pull
- CI pipeline steps that install an SDK or run on a build image with one preinstalled
- The hosting runtime: an App Service runtime stack, the ASP.NET Core Hosting Bundle on IIS, or the runtime installed on a server
- Microsoft packages pinned to 8.x or 9.x: Microsoft.AspNetCore.*, Microsoft.Extensions.*, Microsoft.EntityFrameworkCore.*
- Third-party packages, and whether each has a release that supports .NET 10
- Local .NET tools such as dotnet-ef, which carry their own versions
- Applications you run but did not build: for those, the upgrade is the vendor's, so ask for a .NET 10 version now
04The mechanical change
For each project the core change is a single property. Most of the rest is making the build environment, the container and the packages agree with it.
<!-- every project file, or once in Directory.Build.props -->
<TargetFramework>net10.0</TargetFramework>
// global.json, if the repository pins the SDK
{ "sdk": { "version": "10.0.100", "rollForward": "latestFeature" } }
# Dockerfile
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
FROM mcr.microsoft.com/dotnet/aspnet:10.0Move the Microsoft packages to 10.0.x together, in one commit, and third-party packages in a separate one. EF Core 10 requires the .NET 10 runtime and will not run on anything earlier, so the EF packages cannot be upgraded ahead of the target framework.
05The breaking changes that bite between 8 and 10
Most applications compile against .NET 10 with few or no source changes. The trouble is in the behavioural changes, which compile cleanly and then act differently at runtime. These are the ones most likely to reach a typical ASP.NET Core application, taken from Microsoft's lists for .NET 9 and .NET 10.
- BinaryFormatter always throws (.NET 9)
- Any code that serializes with BinaryFormatter, including inside an older library you depend on, now throws at runtime. Search for it before production finds it for you, and replace it with an explicit serializer.
- Migrations throw on pending model changes (EF Core 9)
- If the model differs from the last migration, Migrate, MigrateAsync and dotnet ef database update now throw instead of quietly applying what they have. Seed data built with DateTime.Now or Guid.NewGuid() in HasData is a common cause, because the model never matches itself.
- Migrations throw inside your own transaction (EF Core 9)
- The once-common pattern of wrapping MigrateAsync in an explicit transaction now raises an error, because EF manages the transaction and a migration lock itself. Remove the outer transaction.
- Contains() translates differently (EF Core 10)
- Parameterized collections, as in ids.Contains(x.Id), are now sent as one SQL parameter per value rather than as a single JSON array. Most queries get better plans; queries over large collections can get worse ones. The previous translation can be restored globally or per query.
- SQL parameter names change (EF Core 10)
- Generated parameter names are simplified, from @__city_0 to @city. The SQL text changes, so expect most cached query plans to recompile on the database server right after deployment, and update any test that compares SQL strings.
- No login redirect for API endpoints (ASP.NET Core 10)
- Endpoints that ASP.NET Core recognises as API endpoints now answer an unauthenticated request with 401 or 403 rather than a redirect to the login page. A front end that waited for a redirect will now see a status code instead.
- Dependency injection is validated at startup in Development (ASP.NET Core 9)
- HostBuilder now enables ValidateOnBuild and ValidateScopes in the Development environment, so a scoped service captured by a singleton, which used to fail late or not at all, fails when the application starts on a developer's machine.
- Container images move to Ubuntu (.NET 10)
- Default tags such as aspnet:10.0 now point to Ubuntu 24.04 images, and Debian images are not published for .NET 10. A Dockerfile that installs packages with apt may need different package names. The .NET 9 images had already stopped installing zlib.
- Restore audits transitive packages (.NET 10 SDK)
- dotnet restore now reports vulnerabilities in transitive dependencies as well as direct ones. A build with warnings treated as errors can fail on the first restore without a single line of code having changed.
- Obsoletions (ASP.NET Core 10)
- WebHostBuilder, Razor runtime compilation, IActionContextAccessor, and IPNetwork with ForwardedHeadersOptions.KnownNetworks are now obsolete. They still work, but each produces a warning, which becomes an error under TreatWarningsAsErrors.
Microsoft describes its .NET 10 list as a work in progress and not a complete list of breaking changes. Treat this table as the likely suspects, not the whole set, and read the source lists below for your own dependencies.
06The order to do it in
The aim is that every step can be verified on its own, so that when something breaks there is one candidate cause rather than five.
- Record the baseline on .NET 8: a green test run, and the response times and error rates the application has today
- Install the .NET 10 SDK in CI and update global.json while still targeting net8.0, which the new SDK builds; this isolates SDK and restore changes, including the new transitive audit
- Retarget every project to net10.0 on one branch, multi-targeting a shared library only if something else that consumes it is staying on .NET 8
- Move the Microsoft packages to 10.0.x, then the third-party packages, each as its own commit
- Fix compile errors and the new warnings, reading each obsoletion message rather than suppressing it
- Apply the EF migrations to a recent copy of the production database, which is where the pending-model-changes exception will surface
- Deploy to a staging environment on the new base image or hosting runtime and compare against the baseline
- Release with the previous build still deployable, so going back is a redeploy of a known artifact rather than a rebuild
07The parts that are not code
An upgrade that builds and passes its tests can still fail on the first deployment because the place it runs was never told. The hosting environment needs the .NET 10 runtime: the matching Hosting Bundle on an IIS server, the .NET 10 stack on App Service, the new base image in a container. Build agents need the new SDK, and developers need an IDE version that supports it.
Visual Studio 2022 is also due a servicing update that marks its .NET 8 and .NET 9 components as out of support. Existing installations are not affected, but it is one more reason not to leave a solution half-moved.
08If it cannot be finished by November 10
Missing the date does not take anything offline. The application runs on, and the risk accrues slowly, one unpatched advisory at a time.
If not everything can move in time, move the services that face the internet first, since they are the ones an unpatched vulnerability can be reached through, and internal batch jobs last. Put a date on the remainder rather than leaving it open, because an end-of-support version that nobody has scheduled to leave tends to stay.
09What this costs
For an application already on .NET 8, the retarget itself is usually the smallest line in the estimate. The cost sits in the behavioural changes the application turns out to depend on, in the packages that have to move with it, and in the testing that proves nothing changed that should not have.
An application still on .NET Framework is a different job. There is no property to change: the route is an incremental migration to modern .NET, which is what the Web Forms guide on this site describes, and it is worth going straight to .NET 10 rather than stopping at a version that is already out of support.
10Sources
Dates and breaking changes are taken from Microsoft's own documentation, which is updated as new issues are found.
- .NET 8 and .NET 9 will reach End of Support on November 10, 2026 (Microsoft .NET Blog)
- Breaking changes in .NET 10
- Breaking changes in .NET 9
- Breaking changes in ASP.NET Core 10
- Breaking changes in ASP.NET Core 9
- Breaking changes in EF Core 10
- Breaking changes in EF Core 9
- Migrate from ASP.NET Core in .NET 9 to .NET 10
Also in guides
- Data modelling
Double-entry accounting schema inside an application database
How to stop storing balances and start recording movements, so that every figure the business reports can be explained by the rows that produced it. The schema, the invariant, why entries are never updated, how to post from application events without double-posting, and what it costs when the ledger gets long.
- Architecture
Multi-tenant row-level security that survives a forgotten WHERE clause
One missing predicate is all it takes for one customer to see another's data. Why application-layer filtering is not a security posture, how to make a shared schema genuinely safe with row-level security, the two settings that decide whether it works at all, and the places isolation still leaks after the database is correct.
- Validation
Parallel run testing: proving re-implemented business logic
How to be sure a rule you rebuilt from a specification behaves like the one it replaces, when the specification is incomplete and the original is unreadable. Where to run the comparison, what to compare, how to read the differences, and what to do when the old system turns out to be wrong.
- Migration
Moving a live ASP.NET Web Forms application to modern .NET
The route-by-route method in full: what has to be true before the first page moves, how one identity and one session are held across two stacks, how re-implemented business logic is proved correct against the original, and what goes wrong. Written to be executed, including by someone who never hires us.
This is the method we use, published in full.
If you would rather not run it yourself, the two-week assessment produces the sequence for your specific system, and the plan is yours whoever executes it.