DEV Community

Ahmed Shah
Ahmed Shah

Posted on

Should You Migrate to .NET 10 Today? An Honest LTS Decision Guide

.NET 8 and .NET 9 both reach end of support on November 10, 2026. That is about five weeks from today.

Many of us still run services on .NET 8. It was the safe LTS choice, and it worked well. So the real question now is not "should we move?" but "how fast, and to which version?"

In this post we will learn about:

  • The dates that matter, from Microsoft's own support policy
  • What "end of support" really means for your apps on Azure
  • A simple decision guide: migrate now, migrate soon, or wait
  • The migration steps we follow, and the pitfalls to watch for

  • .NET 10 is LTS and is supported until November 14, 2028.

  • .NET 8 (LTS) and .NET 9 (STS) end on the same day: November 10, 2026.

  • For most production services, move to .NET 10 now. Do not wait for .NET 11.

The dates that matter

Here are the dates from the official .NET support policy (last updated September 8, 2026):

Version Type Released End of support
.NET 8 LTS Nov 14, 2023 Nov 10, 2026
.NET 9 STS Nov 12, 2024 Nov 10, 2026
.NET 10 LTS Nov 11, 2025 Nov 14, 2028
.NET 11 STS Nov 2026 (RC1 out now)

Note: .NET 9 was first planned to end in May 2026. During its life, Microsoft extended STS support from 18 to 24 months. That is why .NET 8 and .NET 9 now end together.

LTS means Long Term Support (three years). STS means Standard Term Support (now two years). The quality of both is the same. Only the support length is different.

What "end of support" really means

Your app will not stop on November 11. It keeps running.

But after that date:

  • No more security patches for that runtime
  • No Microsoft support if something breaks
  • Security scanners and audits start to flag it

On Azure App Service, apps on an old runtime keep running, but App Service can't give security patches or support for that runtime after its end-of-support date.

On Azure Functions, check two things. .NET 8 and .NET 9 workers end on the same date. And the in-process model for .NET Functions also ends support on November 10, 2026. If you still use in-process Functions, plan the move to the isolated worker model together with the version upgrade.

The decision guide

flowchart TD
    A[Service on .NET 8 or 9] --> B{Is it in production<br/>and still maintained?}
    B -- No, retiring soon --> W[Fine to wait.<br/>Retire it, or isolate it]
    B -- Yes --> C{Do all key packages<br/>support .NET 10?}
    C -- Yes --> N[Migrate now to .NET 10]
    C -- Not yet --> S[Migrate soon.<br/>Track the blocker, upgrade the rest]
    N --> T[Target .NET 10 LTS,<br/>not .NET 11 STS]
    S --> T

Migrate now

  • The service is in production and you will keep changing it.
  • It faces the internet, or it handles payments or personal data.
  • Your main packages (EF Core, your message bus client, your auth library) already support .NET 10.

Migrate soon (but start this week)

  • One important third-party package is not ready yet. Upgrade everything else, and keep a short list of blockers.
  • You have many microservices. Start with one small, low-risk service and learn from it.

It's fine to wait

  • The app will be retired soon, and it is not exposed to the internet.
  • It is a .NET Framework app. That is a different support track and a different (bigger) migration.

Be honest with this last group. "We will retire it next year" often becomes three more years.

Why .NET 10 and not .NET 11?

.NET 11 ships in November 2026, at .NET Conf (November 10 to 13). It is an STS release.

So why not wait and jump straight to .NET 11?

  • .NET 11 GA arrives on the same day .NET 8 support ends. Waiting leaves no buffer for testing.
  • Your packages need time to support a new version. .NET 10 has had almost a year.

If your team likes the newest features and upgrades every year, .NET 11 is a fine next step after you are on .NET 10. Going 8 to 10 now, then 10 to 11 later, is a much smaller jump each time.

Step-by-step migration

These are the steps we follow for one service. The sample config is in my repo (link at the end).

1. Install the SDK and pin it with global.json

Install the latest .NET 10 SDK. Then pin it, so every developer and the CI pipeline use the same SDK.

// global.json
{
  "sdk": {
    "version": "10.0.100",
    "rollForward": "latestFeature"
  }
}
Enter fullscreen mode Exit fullscreen mode

Note: rollForward: latestFeature lets the build use a newer 10.0 SDK feature band if one is installed,

2. Change the target framework in one place

If you have many projects, put the target framework in Directory.Build.props at the solution root. Then one line upgrades every project.

<!-- Directory.Build.props -->
<Project>
  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <CheckSdkVulnerabilities>true</CheckSdkVulnerabilities>
  </PropertyGroup>
</Project>
Enter fullscreen mode Exit fullscreen mode

Note: remove <TargetFramework> from each .csproj, or it will override this value.

CheckSdkVulnerabilities makes the build warn when the SDK you build with has known vulnerabilities (NETSDK1238) or is end of life (NETSDK1239). The check arrives with the .NET 11 SDK (Preview 5 and later); the .NET 10 SDK ignores the property, so today it does nothing. It is a cheap safety net for the next migration.

3. Update the Microsoft packages

Update every Microsoft.* package that follows the runtime version (ASP.NET Core, EF Core, Extensions) to 10.0.x. Then check what else is outdated or vulnerable:

# from the solution folder
dotnet list package --outdated
dotnet list package --vulnerable --include-transitive
Enter fullscreen mode Exit fullscreen mode

4. Read the breaking changes (really)

Microsoft keeps a full list of breaking changes in .NET 10. For microservices, these are the ones I would check first:

  • Default .NET container images use Ubuntu (not Debian). If your Dockerfile runs apt-get or depends on OS packages, test it.
  • System.Linq.AsyncEnumerable is now in the core libraries. If you use the System.Linq.Async package, you can get conflicts.
  • BackgroundService runs all of ExecuteAsync as a Task. Check workers that did blocking work before their first await.
  • Null values are preserved in configuration. Check options classes that relied on nulls being dropped.
  • dotnet restore audits transitive packages. Your build may show new vulnerability warnings, which is a good thing.

Also read ASP.NET Core 10 breaking changes and EF Core 10 breaking changes if you use them.

5. Update the Docker base images

# Dockerfile
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
COPY . .
RUN dotnet publish ./src/OrderService/OrderService.csproj -c Release -o /app

FROM mcr.microsoft.com/dotnet/aspnet:10.0
WORKDIR /app
COPY --from=build /app .
ENTRYPOINT ["dotnet", "OrderService.dll"]
Enter fullscreen mode Exit fullscreen mode

6. Update the CI pipeline

# .github/workflows/build.yml (only the setup step)
- uses: actions/setup-dotnet@v4  
  with:
    global-json-file: global.json
Enter fullscreen mode Exit fullscreen mode

Reading the version from global.json means you change the SDK in one place only.

7. Update the Azure runtime setting

For App Service on Linux, change the stack to .NET 10:

az webapp config set --name <app-name> --resource-group <rg-name> --linux-fx-version "DOTNETCORE|10.0"
Enter fullscreen mode Exit fullscreen mode

For Azure Functions, move to the isolated worker model first if you are still in-process, then target net10.0.

8. Run the tests, then deploy one service first

Run all tests. Then deploy one small service to a test environment and watch logs and metrics for a day before moving the rest.

Should a tool do this for me?

Microsoft now recommends GitHub Copilot app modernization for upgrades. It analyzes your solution, writes an upgrade plan, and commits each change so you can review or roll it back.

The older .NET Upgrade Assistant is officially deprecated. Use it only if you can't use the new agent.

My honest view: a tool can do the boring edits. But you still need to read the breaking changes and test your own service. The tool doesn't know your production traffic.

Key takeaways

  • .NET 8 and .NET 9 both end on November 10, 2026.
  • .NET 10 (LTS, until November 14, 2028) is the safe target for most services.
  • Waiting for .NET 11 leaves you no testing buffer.
  • Pin the SDK with global.json, set the target framework in one place, and read the breaking changes.
  • Start with one small service, then move the rest.

This is a practical guide, not a full list of every change. Always check the official docs for your own stack.

The sample config is on GitHub:
https://github.com/ahmedshahjr/Articles/tree/main/should-you-migrate-to-dotnet-10

Direct link to the sample folder:
In the next post we will look at the new C# 14 field keyword and how it removes a lot of property boilerplate.

Which .NET version are your services running today, and what is the one thing blocking your move to .NET 10?

Top comments (0)