Breaking Changes in .NET 10: A Migration Guide from .NET 8
by Cheyenne Sokkappa, on Jul 27, 2025, 12:00:00 AM
Breaking changes, dependency debt, and where the hours actually go for teams moving off .NET 8 or .NET 9.
.NET 8 and .NET 9 reach end of support on the same day: November 10, 2026. After that date, Microsoft stops providing security updates and technical support for both versions. That leaves most enterprise .NET teams with one practical destination. .NET 10, the current Long Term Support release, shipped November 11, 2025 and is supported through November 14, 2028.
The question that keeps coming up in sprint planning and architecture reviews is what the upgrade will actually take. This guide answers that in terms of effort rather than budget. It covers the breaking changes that matter in the 8 to 10 window, where dependency work tends to stall, what changes in your build pipeline, and what separates a two-day upgrade from a three-week one.
First, confirm which upgrade you’re doing
Teams often assume every move to .NET 10 is the same kind of project, and that assumption is where most estimates go wrong.
If you’re on .NET 8 or .NET 9, you’re moving within the modern .NET runtime. The architecture, SDK toolchain, and compilation model all stay the same. Microsoft designed the annual release cadence for incremental, low-disruption upgrades, and the move from .NET 8 to .NET 10 is a good example of that design working as intended.
If you’re on any version of .NET Framework, you’re facing a platform migration. That project carries a different risk profile and needs a different plan, which we cover at the end of this post. Everything in between assumes you’re starting from .NET 8 or .NET 9.
Key dates for your planning
| Version | Release type | Released | End of support |
|---|---|---|---|
| .NET 8 | LTS | November 14, 2023 | November 10, 2026 |
| .NET 9 | STS | November 12, 2024 | November 10, 2026 |
| .NET 10 | LTS | November 11, 2025 | November 14, 2028 |
| .NET 11 | STS | Expected November 2026 | Projected November 2028 |
.NET 9 was originally scheduled to reach end of support in May 2026. In September 2025, Microsoft extended Standard Term Support releases from 18 to 24 months, starting with .NET 9, which moved its end of support to November 10, 2026. Teams on .NET 9 now face the same deadline as teams on .NET 8.
If you go directly from .NET 8 to .NET 10, you inherit the breaking changes from .NET 9 as well as .NET 10, so plan to review both releases’ changes.
The short answer
For most applications on modern .NET, upgrading to .NET 10 takes less effort than teams budget for. The breaking changes in the 8 to 10 window are targeted and documented, and most of them show up as compiler warnings or analyzer diagnostics at build time. There’s no architectural rework and no wholesale change to the API surface, so most of the effort is mechanical.
The real uncertainty in your estimate comes from two places: test coverage and your dependency graph. Both are solvable, but that’s where surprises tend to appear.
Where the work goes
1. Breaking changes audit (2 to 8 hours)
Start with the breaking changes list below, then check Microsoft’s official lists for .NET 9 and .NET 10. ASP.NET Core and Entity Framework Core maintain their own lists, which are linked from those pages. For an LTS-to-LTS move, the changes are scoped to behavior in specific APIs rather than platform-wide rewrites.
Before you review anything manually, build against the .NET 10 SDK with warnings enabled. Obsoleted APIs surface as SYSLIB diagnostics, and those warnings narrow the list quickly.
For an average application, the audit takes an afternoon. A large but well-structured codebase might take a full day.
2. NuGet dependency compatibility sweep (4 to 16 hours)
This is where teams most consistently underestimate effort, so it’s worth spending time here before the upgrade begins.
Your application code may compile cleanly against .NET 10, but your packages need to as well. Major ecosystem packages such as Entity Framework Core, Serilog, Polly, and Dapper typically ship updates timed to major .NET releases and rarely cause problems.
These are the issues that tend to slow teams down:
- Unmaintained packages. Any package without a release in the past 12 to 18 months is a flag. Check whether the maintainer is still active and whether a .NET 10 target framework moniker appears in recent releases.
- Version pins. If you’ve hard-pinned packages for stability, you may be pulling in versions that predate .NET 10 compatibility.
- Internal packages. Shared libraries your team publishes internally need their own target framework updates, either before or in parallel with the applications that consume them.
- Transitive dependencies. A package you depend on may itself depend on something incompatible. In .NET 10,
dotnet restoreaudits transitive packages by default, so expect new warnings on your first restore.
A clean dependency graph with well-maintained packages takes about half a day. Significant dependency debt with abandoned packages can take a week or more, because some of those packages will need to be forked, replaced, or wrapped.
Finish the dependency audit before committing to a timeline, since it’s the most variable input in the estimate.
3. SDK and build pipeline updates (2 to 4 hours)
This step is mechanical, but it still needs time in the plan:
- Update
global.jsonto pin the .NET 10 SDK version. - Update
<TargetFramework>or<TargetFrameworks>in each project file. - Update Docker base images (for example,
mcr.microsoft.com/dotnet/aspnet:10.0). The default .NET 10 images now use Ubuntu, so check any Dockerfile steps that assume Debian packages or paths. - Update CI/CD pipeline definitions to use .NET 10 SDK images or runners.
- Update build scripts that reference SDK paths or version-specific binaries.
Projects rarely stall at this stage, but the hours still belong in the estimate.
4. Test suite validation (the variable that drives your estimate)
The hard part of this step is rarely the code changes. It’s building enough confidence to ship once those changes are made.
With strong, well-maintained test coverage, the process is straightforward. You update the target framework, run the suite, fix the few failures caused by breaking changes or package behavior, and ship. Teams with solid coverage have finished in about a day.
With thin coverage, you’re validating manually. That means QA cycles, regression testing against production data patterns, and finding issues that automated tests would normally catch. The .NET 10 upgrade doesn’t create these risks. They already existed, and the version change brings them to the surface.
Before you schedule the upgrade, run your test suite as it stands and check coverage on critical paths. If business logic coverage is below 60 to 70 percent, expect to spend more time on validation than on the upgrade itself. In that case, either improve coverage first or budget extra time for manual QA.
5. Optional: new feature adoption
.NET 10 ships meaningful improvements, including runtime performance gains, C# 14 language features, Blazor rendering enhancements, and expanded AOT compilation support.
Adopting these is a separate decision from the version upgrade. You can update your target framework, ship on .NET 10, and leave application logic untouched. Plan new language features, new APIs, and performance-focused refactoring as a second phase, so feature interest doesn’t inflate the upgrade estimate and upgrade pressure doesn’t rush feature planning.
Effort ranges
For a moderately complex application with clean architecture and reasonable test coverage:
| Scenario | Estimated effort |
|---|---|
| Well-maintained dependencies, strong test coverage | 1 to 2 days |
| Average application, some dependency debt, mixed coverage | 3 to 5 days |
| Large application, unmaintained packages, thin coverage | 1 to 3 weeks |
The width of that range has little to do with .NET 10 itself. It reflects earlier investment, or the lack of it, in dependency hygiene and test infrastructure.
Comprehensive List of Breaking Changes
.NET 10 introduces breaking changes across various areas, primarily to improve performance, security, and consistency. These are documented officially and grouped by technology area below. Migrating from .NET 8 means addressing changes from both .NET 9 and .NET 10. This list includes updates from Preview 6, marked accordingly.
Containers
|
Change Title |
Description |
Reason |
Migration Advice |
Introduced In |
|
Default .NET images use Ubuntu |
Default container images now use Ubuntu. |
Standardization and support. |
Update Dockerfiles; test for Ubuntu-specific behaviors. |
Earlier Preview |
Core .NET Libraries
|
Change Title |
Description |
Reason |
Migration Advice |
Introduced In |
|
ActivitySource.CreateActivity and ActivitySource.StartActivity behavior change |
Altered behavior for activity creation and starting. |
Improved diagnostics consistency. |
Review and update tracing code; test for propagation issues. |
Earlier Preview |
|
C# 14 overload resolution with span parameters |
Changes in overload resolution for spans. |
Enhanced type inference rules. |
Resolve ambiguities by explicit casting or using ReadOnlySpan<T> over Span<T>. |
Earlier Preview |
|
Consistent shift behavior in generic math |
Uniform shift operations in generic math. |
Standardization. |
Update generic math code to align with new behaviors. |
Earlier Preview |
|
Default trace context propagator updated to W3C standard |
Propagator now follows W3C specs. |
Compliance with industry standards. |
Ensure distributed tracing tools are W3C-compatible. |
Earlier Preview |
|
DriveInfo.DriveFormat returns Linux filesystem types |
Now reports accurate Linux FS types. |
Accuracy on Linux. |
Adjust platform-specific file handling code. |
Preview 6 |
|
LDAP DirectoryControl parsing is more stringent |
Stricter parsing rules. |
Security enhancements. |
Validate LDAP controls; fix invalid formats. |
Earlier Preview |
|
MacCatalyst version normalization |
Updated version handling for MacCatalyst. |
Platform alignment. |
Update version checks in cross-platform code. |
Earlier Preview |
|
.NET runtime no longer provides default SIGTERM signal handler |
Removed default handler. |
Customization flexibility. |
Implement custom SIGTERM handling if needed. |
Earlier Preview |
|
System.Linq.AsyncEnumerable included in core libraries |
Now part of core; requires source changes. |
Library consolidation. |
Update imports and resolve namespace conflicts. |
Earlier Preview |
|
YMM embedded rounding removed from AVX10.2 |
Removed support for YMM rounding. |
Hardware optimization. |
Rewrite vectorized code avoiding this feature. |
Earlier Preview |
Extensions
|
Change Title |
Description |
Reason |
Migration Advice |
Introduced In |
|
ProviderAliasAttribute moved to Microsoft.Extensions.Logging.Abstractions assembly |
Attribute moved to a different assembly. |
Assembly reorganization. |
Update references to the new assembly. |
Earlier Preview |
Globalization
|
Change Title |
Description |
Reason |
Migration Advice |
Introduced In |
|
Environment variable renamed to DOTNET_ICU_VERSION_OVERRIDE |
Renamed ICU override variable. |
Clarity. |
Update env vars in configs/scripts. |
Earlier Preview |
Cryptography
|
Change Title |
Description |
Reason |
Migration Advice |
Introduced In |
|
OpenSSL cryptographic primitives aren't supported on macOS |
Removed OpenSSL support on macOS. |
Platform-native crypto preference. |
Switch to Apple Crypto APIs. |
Preview 6 |
|
X500DistinguishedName validation is stricter |
Tighter validation rules. |
Security. |
Fix invalid distinguished names in certs. |
Earlier Preview |
|
X509Certificate and PublicKey key parameters can be null |
Parameters now nullable. |
Flexibility. |
Add null checks in certificate handling. |
Earlier Preview |
Interop
|
Change Title |
Description |
Reason |
Migration Advice |
Introduced In |
|
Single-file apps no longer look for native libraries in executable directory |
Changed search paths for native libraries in single-file apps. |
Security and consistency. |
Bundle natives or adjust load paths. |
Preview 6 |
|
Specifying DllImportSearchPath.AssemblyDirectory only searches the assembly directory |
Limits search to assembly directory when specified. |
Predictability. |
Place libraries in assembly directory or adjust paths. |
Preview 5 |
Networking
|
Change Title |
Description |
Reason |
Migration Advice |
Introduced In |
|
HttpClient/SSLStream default certificate revocation check mode changed to Online |
Default now Online for revocation checks. |
Improved security. |
Set to Offline if needed. |
Preview 6 |
|
Streaming HTTP responses enabled by default in browser HTTP clients |
Streaming now default in browser clients. |
Performance. |
Disable streaming if required. |
Preview 3 |
SDK and MSBuild
|
Change Title |
Description |
Reason |
Migration Advice |
Introduced In |
|
.NET CLI --interactive defaults to true in user scenarios |
Defaults to interactive for user ops. |
Security (user consent). |
Set --interactive false for automation. |
Preview 3 |
|
Default workload configuration from 'loose manifests' to 'workload sets' mode |
Switched default mode. |
Improved management. |
Update to 'workload sets'. |
Preview 2 |
|
dotnet package list performs restore |
Now runs restore before listing. |
Accurate listing. |
Ensure restore prereqs met. |
Preview 4 |
|
dotnet restore audits transitive packages |
Audits transitive deps by default. |
Security. |
Address audit findings. |
Preview 3 |
|
MSBUILDCUSTOMBUILDEVENTWARNING escape hatch removed |
Removed env var for suppressing warnings. |
Enforce checks. |
Handle warnings directly. |
Preview 1 |
|
MSBuild custom culture resource handling |
Changed localization handling. |
Consistency/performance. |
Update scripts for new behavior. |
Preview 1 |
|
NU1510 is raised for direct references pruned by NuGet |
Warning for pruned references. |
Inform devs. |
Fix references or suppress. |
Preview 1 |
|
PackageReference without a version raises an error |
Requires version in PackageReference. |
Better dependency management. |
Specify versions. |
Earlier Preview |
ASP.NET Core
|
Change Title |
Description |
Reason |
Migration Advice |
Introduced In |
|
Response Streaming Enabled by Default for HttpClient Requests |
Streaming now default in Blazor. |
Performance. |
Opt-out via project file or env var for sync ops. |
Earlier Preview |
|
NavigationManager.NavigateTo During Static SSR No Longer Throws NavigationException |
No exception on navigation. |
Consistency with interactive rendering. |
Remove exception-handling code; use AppContext switch to revert. |
Earlier Preview |
|
BlazorCacheBootResources MSBuild Property Removed |
Property removed as files are now fingerprinted. |
Optimization. |
Remove the property from project files. |
Earlier Preview |
|
OpenAPI 3.1 Support and Breaking Changes |
Updated to OpenAPI.NET v2.0; interfaces for entities, removed Nullable prop. |
Standard compliance. |
Update transformers to use JsonNode; check nullability via Type. |
Earlier Preview |
|
New JSON Patch Implementation with System.Text.Json |
Replaced Newtonsoft with System.Text.Json. |
Performance and security. |
Install new package; avoid dynamic types like ExpandoObject. |
Earlier Preview |
Entity Framework Core (EF Core 10)
|
Change Title |
Description |
Reason |
Migration Advice |
Introduced In |
|
ExecuteUpdateAsync now accepts a regular, non-expression lambda |
Switched to Func<> over Expression<>. |
Simpler syntax. |
Rewrite dynamic expressions as funcs. |
Earlier Preview |
|
Using GetDateTimeOffset without an offset now assumes UTC (Sqlite) |
Assumes UTC for offset-less timestamps. |
Consistency. |
Adjust time zone logic; use AppContext switch to revert. |
Earlier Preview |
|
Writing DateTimeOffset into REAL column now writes in UTC (Sqlite) |
Converts to UTC before writing. |
Standardization. |
Handle UTC in queries; revert via switch. |
Earlier Preview |
|
Using GetDateTime with an offset now returns value in UTC (Sqlite) |
Returns UTC-kind DateTime. |
Accuracy. |
Update to expect UTC; revert via switch. |
Earlier Preview |
C# Compiler
|
Change Title |
Description |
Reason |
Migration Advice |
Introduced In |
|
scoped in a lambda parameter list is now always a modifier |
Treated as modifier, not type. |
Language evolution. |
Prefix type with @scoped. |
Earlier Preview |
|
Span<T> and ReadOnlySpan<T> overloads applicable in more scenarios |
New conversions cause ambiguities. |
Improved inference. |
Explicit casts or prioritize ReadOnlySpan. |
Earlier Preview |
|
Diagnostics now reported for pattern-based disposal in foreach |
Obsolete DisposeAsync reported. |
Better warnings. |
Update or suppress obsolete methods. |
Earlier Preview |
|
Set state of enumerator object to "after" during disposal |
MoveNext() returns false post-disposal. |
Correctness. |
Avoid resuming disposed enumerators. |
Earlier Preview |
|
UnscopedRefAttribute cannot be used with old ref safety rules |
Fails in older targets. |
Compatibility. |
Upgrade language version or avoid attribute. |
Earlier Preview |
|
Microsoft.CodeAnalysis.EmbeddedAttribute validated on declaration |
Strict shape validation. |
Enforcement. |
Ensure attribute matches required form. |
Earlier Preview |
|
Expression field in property accessor refers to synthesized backing field |
field keyword binds to backing field. |
Clarity. |
Rename conflicting members or use @field. |
Earlier Preview |
|
Variable named field disallowed in property accessor |
Can't declare local/param as field. |
Avoid conflicts. |
Rename variable. |
Earlier Preview |
|
record and record struct types cannot define pointer type members |
Pointer fields disallowed. |
Safety. |
Refactor to avoid pointers in records. |
Earlier Preview |
Other areas like Windows Forms (e.g., obsolete APIs) may apply; check official docs for full details.
Code Migration Patterns
Migrating code involves common patterns to address breaking changes:
- Project File Updates: Change <TargetFramework>net8.0</TargetFramework> to <TargetFramework>net10.0</TargetFramework>. For multi-targeting, use <TargetFrameworks>net8.0;net10.0</TargetFrameworks>.
- Deprecated API Replacement:
- Before (.NET 8): var activity = ActivitySource.StartActivity("Old");
- After (.NET 10): Review and adapt to new behaviors, e.g., ensure W3C compliance: var activity = ActivitySource.StartActivity("New", kind: ActivityKind.Internal);
- Nullability and Validation Fixes:
- Before: X509Certificate cert = new(key); (assuming non-null)
- After: X509Certificate cert = new(key ?? throw new ArgumentNullException());
- Async/Streaming Adjustments:
- For Blazor streaming: Add opt-out if sync needed: request.SetBrowserResponseStreamingEnabled(false);
- Time Zone Handling in EF Core:
- Before: var dt = reader.GetDateTimeOffset(0); (local assumption)
- After: Adjust for UTC: var dtUtc = reader.GetDateTimeOffset(0).ToUniversalTime();
- New in Preview 6 Examples:
- For DriveInfo on Linux: Update checks like if (drive.DriveFormat == "ext4") to handle new accurate formats.
- For OpenSSL on macOS: Switch to SecureTransport or equivalent: Use SSLProtocols without OpenSSL dependencies.
- For single-file native libs: Use NativeLibrary.Load with explicit paths or bundle properly.
- For revocation checks: Set SslClientAuthenticationOptions.RemoteCertificateValidationCallback to customize if Online mode causes issues.
Run dotnet build with warnings as errors to catch issues early.
Tool Support for Automated Migration
The .NET Upgrade Assistant is the primary tool for automating migrations. It's available as a Visual Studio extension or CLI (dotnet tool install -g upgrade-assistant).
- How to Use: Install via VS Marketplace or CLI. Right-click project in VS > "Upgrade" or run upgrade-assistant upgrade MyProject.csproj. Choose in-place, side-by-side, or incremental mode.
- Features: Analyzes code, updates project files, fixes incompatibilities, and generates reports. Supports ASP.NET, console apps, libraries, and more.
- For .NET 8 to .NET 10: Update the tool to the newest version for Preview 6 support. Combine with Roslyn analyzers for custom fixes.
Other tools include try-convert for partial rewrites.
Common Pitfalls and How to Avoid Them
Treating a clean build as a finished upgrade. Many changes in this window are behavioral, so code compiles and then behaves differently at runtime. Trace context propagation, signal handling, and SQLite date handling in EF Core all fall into this category. Integration tests that use real configuration and real payloads catch what the compiler can’t.
Discovering dependency problems mid-upgrade. Run the dependency audit and dotnet list package --outdated before scheduling the work, and confirm that every package on your critical path ships a .NET 10 compatible version.
Testing on only one platform. The Ubuntu container image change, Linux filesystem reporting, and the removal of OpenSSL support on macOS each affect cross-platform code differently. Run CI on every operating system you deploy to.
Upgrading a large solution all at once. For large solutions, upgrade shared libraries first, multi-target them during the transition, and move applications in waves. Each wave then has a smaller blast radius and a clearer rollback path.
Tooling for the Upgrade
Microsoft has officially deprecated .NET Upgrade Assistant and now points teams to the GitHub Copilot app modernization chat agent, included with Visual Studio 2026 and Visual Studio 2022 17.14.16 or later. The agent analyzes projects and dependencies, produces a step-by-step plan with automated code fixes, and commits each change so you can validate it or roll it back. It requires a paid GitHub Copilot subscription, which the older Upgrade Assistant did not. We covered the transition in more detail in What Happened to the .NET Upgrade Assistant?
For a .NET 8 to .NET 10 upgrade, the SDK does much of the work on its own. Build-time diagnostics, dotnet restore auditing, and your existing test suite will surface most issues without additional tooling.
Should You Wait for .NET 11?
.NET 11 is expected in November 2026 as a Standard Term Support release. Under the 24-month STS policy, its support is projected to end around November 2028, about when .NET 10’s support also ends. Waiting for .NET 11 would add no runway. It would put you on a brand-new release, and it would leave .NET 8 or .NET 9 unpatched while you wait.
For most enterprise teams, .NET 10 is the right target now. .NET 11 features can be evaluated later, separately from this deadline.
If You’re on .NET Framework, This Is a Different Project
If the effort estimates above felt like a relief, that’s the modern .NET versioning model working as designed.
Many teams asking what a .NET 10 upgrade costs are actually running .NET Framework and hoping for a similar answer. Moving from .NET Framework to modern .NET is a platform migration. It involves a different runtime, a different hosting model, a different deployment story, and, in most cases, a different API surface that requires application-level changes. Applications that depend on WebForms or WCF, and older applications built on VB6 or PowerBuilder, often have no supported path forward without specialized help. That effort starts at weeks and scales with application size and complexity.
For those teams, the .NET 10 upgrade timeline isn’t the constraint. The modernization roadmap is, and it’s worth planning before the November deadline.
Frequently Asked Questions
When does .NET 8 support end?
November 10, 2026. .NET 9 reaches end of support on the same date.
Can I skip .NET 9 and upgrade straight from .NET 8 to .NET 10?
Yes, and most teams do. Review the breaking changes for both .NET 9 and .NET 10, because skipping a version means inheriting its changes.
How long does a .NET 8 to .NET 10 upgrade take?
For a moderately complex application, anywhere from one or two days to about three weeks. The range depends mostly on dependency health and test coverage, not on .NET 10 itself.
What replaced the .NET Upgrade Assistant?
Microsoft now recommends the GitHub Copilot app modernization chat agent in Visual Studio, which requires a GitHub Copilot subscription.
Is upgrading from .NET Framework the same as upgrading from .NET 8?
No. .NET Framework to modern .NET is a platform migration with a different runtime and API surface, and it typically requires application-level changes.


