.NET Core End of Support:
Upgrade old .NET apps to a supported release.

Microsoft has ended support for .NET Core 2.1 and 3.1 and for .NET 5, 6 and 7. .NET 8 and .NET 9 both have an end-of-support date of November 10, 2026. .NET 10 is the current long-term support release, supported until November 14, 2028. Most apps need a planned version upgrade, not a rewrite. The assessment decides the route and flags the few that need more.

Book a free 1-hour consultation →

At a glance

Legacy sources: .NET Core 2.1 · .NET Core 3.1 · .NET 5 · .NET 6 · .NET 7 · .NET 8 · .NET 9 · ASP.NET Core with Startup.cs
Modern targets: .NET 10 (LTS) · ASP.NET Core minimal hosting · Entity Framework Core 10 · System.Text.Json · Current Linux container base images · NuGet audit in CI · Domain model with events (where it pays)
First step: Free 1-hour consultation, then a fixed-scope assessment
U.S. delivery: 100% U.S. citizen workforce, U.S.-soil delivery

What to know before you modernize

Why .NET Core apps stall on old versions

Most stalled .NET Core apps were healthy when they shipped. The team moved on, the app kept working, and each new release added a small tax: a package that stopped targeting the old framework, a base image that changed its tags, a breaking change in Entity Framework Core. Skipping a release makes the next jump bigger, because the breaking-change lists stack up. Three causes show up most often. Nobody owns the upgrade, so it never gets a budget. Test coverage is thin, so every change feels risky. And a deprecated dependency, often an identity server or a serialization library, blocks the framework version. None of these is a reason to rewrite. They are the work list an upgrade has to clear, and they get cheaper to clear the earlier you start, because the supported window keeps moving forward whether or not the app does.

Why not to rewrite by default →

Where each .NET version stands today

Microsoft's support policy page lists the dates. .NET Core 2.1 ended on August 21, 2021. .NET Core 3.1 ended on December 13, 2022. .NET 5 ended on May 10, 2022, .NET 7 on May 14, 2024, and .NET 6 on November 12, 2024. .NET 8, a long-term support release, and .NET 9, a standard-term support release, both end on November 10, 2026. .NET 10 shipped on November 11, 2025 as a long-term support release and is supported until November 14, 2028. The policy gives long-term releases three years of support and standard-term releases two years in total, with the final six months of any release limited to security fixes. If your app runs .NET 8 or .NET 9, plan around November 10, 2026, not a later date. Check the policy page before you plan, because dates and phases are updated there.

Have the assessment check your versions →

What out of support means for a running app

An out-of-support runtime keeps running. Nothing stops on the end date. What stops is the free support and patches that the policy ties to the supported window, so every newly found runtime or framework vulnerability stays open in your environment. That matters most for internet-facing apps, for apps that handle payments or personal data, and for anything a customer or auditor scans. It also matters indirectly: NuGet packages drop old target frameworks, vendors stop testing against them, and the base images you build on age out of the registries you rely on. The risk is real but graded. An internal batch job on a locked-down network carries less exposure than a public API. The assessment rates each application by exposure and data sensitivity, so the order of upgrades follows risk, not the order of the solution file.

When not to modernize →

One LTS at a time, or straight to .NET 10

Microsoft publishes breaking changes per release and per migration path, and the tooling targets the latest version. For a small service on .NET 6 or 7, going straight to .NET 10 is usually fine: change the target framework, update packages, fix what the compiler and tests report. For a large app on .NET Core 3.1 with Entity Framework Core 3.x and IdentityServer4, we typically recommend stepping through one long-term release first, for example 3.1 to 6, then 6 to 10, so each step has a short, readable list of failures and a green test run between steps. The newer SDK can still build older targets. Microsoft states the 10.0.100 SDK builds from net9.0 down to netcoreapp1.0, so you can move the SDK and CI first and change the target framework as a separate commit. The .NET Upgrade Assistant is now deprecated; Microsoft points to the GitHub Copilot modernization agent in Visual Studio 2026 and Visual Studio 2022 17.14.16 or later.

What breaks in ASP.NET Core, JSON and identity

The changes cluster in a few places. In ASP.NET Core 3.0, Newtonsoft.Json left the shared framework and System.Text.Json became the default serializer. Apps that need Json.NET behavior add the Microsoft.AspNetCore.Mvc.NewtonsoftJson package, which is a valid choice if it is a deliberate one. The minimal hosting model arrived in .NET 6, but it is optional: Microsoft states that Startup with the generic host is fully supported and that you do not have to migrate. Removing Startup is a cleanup, not a requirement. Razor views no longer compile into a separate Views assembly, which affects apps that discover types by assembly. IdentityServer4 is archived and went out of support when .NET Core 3.1 did. Its successor, Duende IdentityServer, is a commercial product with a Community Edition, so budget for a licensing decision or move to a hosted identity provider.

Entity Framework Core is the biggest data-layer risk

If the app uses EF Core 3.x or earlier, read the breaking-changes page for 3.x before anything else. EF Core 3.0 stopped evaluating LINQ queries on the client, so queries that silently pulled whole tables into memory now throw or must be rewritten. The dotnet ef tool stopped shipping with the SDK and became a separate install. The raw SQL methods were renamed, and FromSql can no longer be composed over stored procedures. Eager loading now produces a single query, which can change both performance and results on large graphs. Cascade deletes now run immediately by default. Each of these can change behavior without a compile error, which is why query-level tests matter more here than anywhere else. Later EF Core releases publish their own breaking-change lists, and the dotnet/efcore repository tracks them with a breaking-change label. Read the list for each version you cross, and run migrations against a copy of production data before cutover.

NuGet health, Docker images and CI/CD

Before changing the target framework, inventory the packages. Run dotnet list package with the vulnerable, deprecated and outdated options, and turn on NuGet audit: since the .NET 8 SDK, restore warns about known vulnerabilities, drawn from the GitHub Advisory Database for nuget.org. Replace deprecated packages first, since they are the usual blocker. Then fix the build chain. Dockerfiles that name mcr.microsoft.com/dotnet/aspnet:5.0 or 6.0 need the new tags, and hosts on IIS need a current Hosting Bundle for the ASP.NET Core Module. Pin the SDK with global.json, build with dotnet build in CI, and add a restore step that fails on high and critical advisories in a dedicated audit pipeline, as Microsoft documents. Azure Functions on v1 to v3 move to v4 on the isolated worker model. Do this inventory once, write it down, and the upgrade becomes a sequence of small pull requests.

Tests are the safety net

A version upgrade changes behavior in small ways that compilers do not catch: serialization defaults, query translation, time zone and culture handling, middleware order. The safety net is a test suite that exercises the app's real behavior. If it exists, keep it green at every step. If it does not, write characterization tests before upgrading: record what the app does today at its HTTP boundary and in its key queries, then assert the same outputs after each step. WebApplicationFactory and TestServer still work with the minimal hosting model, so integration tests written against the old Startup keep working. Run the tests against a copy of production data where you can, because many EF Core differences only appear with real volumes and real nulls. Treat a missing test suite as the first deliverable, not a reason to skip the upgrade.

How we compare old and new behavior →

When a version bump is the whole answer

Most of the time it is. If the app's design is sound and the pain is an expired runtime, a pure upgrade to .NET 10 is the right answer, and adding architecture on top of it adds risk without a reason. The follow-on question comes up in a minority of apps: the code is CRUD over tables that grew complicated, with status flags, copy-pasted rules, and reports that cannot explain how a record got to its current state. There, a domain model can be worth it, and event sourcing for one part of it only if you need history, audit or replay for that part. Do the version upgrade first, so you are not changing the platform and the design in one move. Then decide per bounded context. Reference data, settings and simple lookups should stay as tables.

Event sourcing for existing .NET systems →

Start with an assessment for a .NET Core codebase

The Legacy Software Assessment is a paid, fixed-scope, read-only review that follows a free one-hour consultation. For a .NET Core or .NET codebase it inventories every solution and its target framework, the SDK and build chain, and the container and hosting setup. It lists each NuGet package with its vulnerability and deprecation status, finds IdentityServer4, Newtonsoft.Json and other framework-coupled dependencies, and maps the EF Core version and its query risks. It rates the existing tests and states which behavior needs characterization tests first. It reports each application's exposure and data sensitivity, recommends direct or stepped upgrade per application, and says plainly where a modernized core is not worth it. The fee is quoted after the consultation and is creditable toward a later modernization project.

About the assessment →

Deliverables

  • A dated inventory of every application, target framework and SDK
  • A package report with vulnerable, deprecated and outdated dependencies
  • An upgrade route per application: direct to .NET 10 or stepped
  • A list of breaking changes that apply, with EF Core and identity risks flagged
  • A test plan covering the characterization tests needed before upgrading
  • A recommendation on whether any part is worth a domain model or event sourcing

Common questions

When does .NET 8 support end?

Support for .NET 8 ends on November 10, 2026, the same date as .NET 9. .NET 8 is a long-term support release, and its final six months carry security fixes only. After November 10, 2026, Microsoft's free support and patches end. .NET 10 is the current long-term support release, supported until November 14, 2028. Verify the dates on Microsoft's .NET support policy page.

Is .NET Core 3.1 still supported?

No. Microsoft's policy lists December 13, 2022 as the end of support for .NET Core 3.1. .NET 5 ended on May 10, 2022, .NET 6 on November 12, 2024 and .NET 7 on May 14, 2024. Apps on these versions still run, but they no longer receive free support or patches from Microsoft.

Do we have to upgrade or can we keep running an old .NET version?

It keeps running. The end date removes free support and security patches, not the ability to execute. Whether to accept that depends on exposure: an internet-facing API or an app with personal data should move soon, while an isolated internal job can wait. The assessment rates each application by exposure and data sensitivity so the order is based on risk.

Can we go straight from .NET Core 3.1 to .NET 10?

Sometimes, but for large apps we typically recommend one long-term release in between, for example 3.1 to 6 and then 6 to 10. Each step then has a short breaking-change list and a green test run. Small services with few dependencies can often go directly. The newer SDK can build older targets, so you can update tooling first.

What breaks most often when upgrading .NET Core?

The common failures are Entity Framework Core query behavior, especially the end of client-side evaluation in EF Core 3.0, serialization defaults after Newtonsoft.Json left the ASP.NET Core shared framework, IdentityServer4 and other unsupported packages, and base image or hosting changes. Startup.cs is not one of them, since Microsoft says it is fully supported.

Do we have to remove Startup.cs and move to minimal hosting?

No. Microsoft states that apps migrating to .NET 6 or later do not need the minimal hosting model, and that Startup with the generic host remains fully supported. You can change the target framework and keep your existing hosting code. Moving to minimal hosting is a later cleanup that you can do when it helps.

How long will a .NET upgrade take and what does it cost?

It depends on the number of applications, the EF Core and identity dependencies, and the test coverage. A free one-hour consultation comes first. Price and schedule are fixed after the fixed-scope assessment, and the assessment fee is creditable toward a modernization project. Project work ranges from a few weeks (virtualization) to several months (re-architecture).

Do we have to rewrite the app or add event sourcing?

No. A version upgrade to .NET 10 is often the whole answer. Event sourcing is worth considering only for a part of the system that needs history, audit or replay, and it is decided per bounded context after the upgrade. Reference data, settings and simple CRUD should stay as tables.

Sources

Not sure where to start?

Start with a fixed-scope Legacy Software Assessment: a read-only review of your application’s source, database, configuration, logs, and architecture, ending in findings with evidence and a written recommendation. Quoted after the free 1-hour consultation; creditable toward a subsequent modernization project.