How to Migrate .NET Projects Between Versions: A Complete .NET 8 to .NET 9 Upgrade Guide
The official dotnet-upgrade skill in the .NET Skills repository provides a six-phase workflow to migrate .NET projects between versions, specifically from .NET 8 to .NET 9, covering assessment, framework updates, build-error remediation, behavioral changes, infrastructure updates, and final verification.
Migrating between major .NET versions requires systematic handling of breaking changes, API updates, and infrastructure modifications. The dotnet/skills repository contains the authoritative migration skill at plugins/dotnet-upgrade/skills/migrate-dotnet8-to-dotnet9/SKILL.md, which provides a battle-tested workflow for upgrading your projects while maintaining build stability and runtime correctness.
Phase 1: Assess Your .NET 8 Project
Before modifying any code, establish a baseline understanding of your project's current state. According to the skill's assessment checklist (lines 55-62), perform these four steps:
- Identify entry points – Locate all
.csproj,.sln, and.slnxfiles that require upgrading. - Confirm SDK availability – Run
dotnet --versionto verify you have the .NET 9 SDK (9.0.x) installed. - Detect technology surface – Examine the SDK attribute (
Microsoft.NET.Sdk.Web,Microsoft.NET.Sdk.WindowsDesktop) andPackageReferenceelements to determine which reference documents apply to your stack (ASP.NET Core, EF Core, WinForms/WPF, containers). - Baseline build – Execute a clean build on the current
net8.0target to capture existing warnings and establish a working state.
Phase 2: Update the Target Framework to .NET 9
Modify your project files to target the new runtime. In each .csproj file (or Directory.Build.props), update the TargetFramework element:
<!-- Before -->
<TargetFramework>net8.0</TargetFramework>
<!-- After -->
<TargetFramework>net9.0</TargetFramework>
For multi-targeted projects, add net9.0 to the <TargetFrameworks> element or replace the existing entry. Then update all Microsoft package references to the 9.0.x stream:
dotnet restore
dotnet build --no-incremental
Critical requirement: Visual Studio 2022 version 17.12 or later is required to build net9.0 targets (see Step 2, lines 79-81 in the skill file).
Phase 3: Resolve Build Errors and Breaking Changes
After the framework bump, compilation errors and new warnings will emerge. The skill references specific documentation based on your technology stack (lines 88-100). Address these common breaking changes:
params ReadOnlySpan Overload Ambiguity
New params ReadOnlySpan<T> overloads in .NET 9 can cause ambiguous method invocation errors. Fix: Cast arguments explicitly or use specific overloads. Reference: core-libraries-dotnet8to9.md.
BinaryFormatter Runtime Exceptions
BinaryFormatter now throws PlatformNotSupportedException at runtime. Fix: Replace with System.Text.Json, MessagePack, or custom serializers. Reference: serialization-networking-dotnet8to9.md.
SYSLIB0054-SYSLIB0057 Obsoletions
New obsoletions affect APIs like Thread.VolatileRead. Fix: Switch to Volatile.Read or Volatile.Write. Reference: core-libraries-dotnet8to9.md.
C# 13 InlineArray Restrictions
Applying InlineArray to record structs is now disallowed. Fix: Convert to a standard struct or remove the attribute. Reference: csharp-compiler-dotnet8to9.md.
String.Trim Overload Removal
The String.Trim(ReadOnlySpan<char>) overload has been removed. Fix: Use overloads accepting char[] or string parameters instead.
Iteratively rebuild after each batch of fixes until dotnet build succeeds with zero errors.
Phase 4: Address Behavioral Changes in .NET 9
These runtime changes don't break compilation but may alter application semantics:
| Change | Impact | Action |
|---|---|---|
| Floating-point saturation | Conversions from floating-point to integer now saturate instead of wrapping | Add unit tests for boundary value conversions |
| EF Core strict migrations | Migrate() throws on pending model changes |
Replace DateTime.Now or Guid.NewGuid() in seed data with fixed constants |
| HttpClientFactory defaults | Defaults to SocketsHttpHandler instead of HttpClientHandler |
Update casting logic or use ConfigurePrimaryHttpMessageHandler |
| DI validation enabled | Development mode now validates service configuration | Run in Development mode to surface configuration errors early |
The skill's Step 4 (lines 31-69) contains the complete behavioral change inventory.
Phase 5: Update Infrastructure and Tooling
Container Images
Update your Dockerfile to use .NET 9 base images. Note that .NET 9 images no longer include zlib by default:
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
FROM mcr.microsoft.com/dotnet/aspnet:9.0
# If your application requires zlib:
RUN apt-get update && apt-get install -y zlib1g
CI/CD Configuration
Update global.json to specify the .NET 9 SDK:
{
"sdk": {
"version": "9.0.100",
"rollForward": "latestFeature"
}
}
Additionally, MSBuild now emits a Terminal Logger by default. CI environments parsing console output may need to disable this:
dotnet build --tl:off
# Or set environment variable:
export MSBUILDTERMINALLOGGER=off
Tooling Requirements
Ensure Visual Studio 2022 17.12+ is installed on all development and build machines before attempting to build net9.0 targets.
Phase 6: Verify the Migration
Complete the migration with comprehensive verification:
- Clean build:
dotnet build --no-incremental - Test execution:
dotnet test - Container validation: Rebuild and run Docker images
- Smoke testing: Verify these specific areas:
BinaryFormatterusage (will now throw)- Saturating numeric conversions
- EF Core migration behavior
- HttpClientFactory handler compatibility
- Dependency injection validation errors
Review the final diff to ensure no unintended changes were introduced (see Step 6, lines 100-106).
Reference Documentation for .NET 9 Migration
The skill provides detailed reference files for specific technology areas:
| File | Contents |
|---|---|
plugins/dotnet-upgrade/skills/migrate-dotnet8-to-dotnet9/SKILL.md |
Primary migration workflow |
references/csharp-compiler-dotnet8to9.md |
C# 13 compiler breaking changes |
| references/core-libraries-dotnet8to9.md | Core library API changes and SYSLIB warnings |
| references/serialization-networking-dotnet8to9.md | BinaryFormatter, HttpClient, and networking changes |
| references/aspnet-core-dotnet8to9.md | ASP.NET Core DI and configuration changes |
| references/efcore-dotnet8to9.md | Entity Framework Core migration changes |
| references/containers-interop-dotnet8to9.md | Docker and native interop updates |
Summary
- Assessment is critical: establish a baseline build and identify your technology surface before touching code.
- Framework update requires Visual Studio 17.12+ and updating both
TargetFrameworkand package references to9.0.x. - Build errors span compiler changes, API removals (like
BinaryFormatter), and new overload ambiguities requiring explicit code fixes. - Behavioral changes affect runtime semantics including numeric conversions, EF Core migrations, and HTTP client handling.
- Infrastructure updates cover Docker base images (
mcr.microsoft.com/dotnet/sdk:9.0),global.jsonSDK versions, and CI/CD Terminal Logger configuration. - Verification must include smoke testing for removed serializers and changed default behaviors.
Frequently Asked Questions
Can I upgrade directly from .NET 6 to .NET 9?
While technically possible by changing the target framework, Microsoft recommends migrating through intermediate versions (6 → 8 → 9) to isolate breaking changes. The dotnet/skills repository provides specific migration skills for each version transition, allowing you to address breaking changes incrementally rather than all at once.
Do I need to update Visual Studio before upgrading to .NET 9?
Yes. Visual Studio 2022 version 17.12 or later is strictly required to build net9.0 targets. Attempting to build .NET 9 projects in earlier versions will result in SDK resolution errors. The build infrastructure will also need the .NET 9 SDK installed.
How do I handle BinaryFormatter removal in .NET 9?
The BinaryFormatter now throws PlatformNotSupportedException at runtime. Replace serialization logic with System.Text.Json, MessagePack, or protocol buffers. For legacy data migration, implement a custom deserializer that reads the binary format and converts to the new serialization format. Reference the serialization-networking-dotnet8to9.md file for specific migration patterns.
What are the main behavioral changes in .NET 9 that affect runtime?
The four critical runtime changes are: floating-point to integer conversions now saturate instead of wrap around; EF Core's Migrate() method throws exceptions when pending model changes exist; HttpClientFactory defaults to SocketsHttpHandler rather than HttpClientHandler; and dependency injection validation is enabled by default in Development mode. Test these areas thoroughly after migration.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →