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:

  1. Identify entry points – Locate all .csproj, .sln, and .slnx files that require upgrading.
  2. Confirm SDK availability – Run dotnet --version to verify you have the .NET 9 SDK (9.0.x) installed.
  3. Detect technology surface – Examine the SDK attribute (Microsoft.NET.Sdk.Web, Microsoft.NET.Sdk.WindowsDesktop) and PackageReference elements to determine which reference documents apply to your stack (ASP.NET Core, EF Core, WinForms/WPF, containers).
  4. Baseline build – Execute a clean build on the current net8.0 target 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:

  1. Clean build: dotnet build --no-incremental
  2. Test execution: dotnet test
  3. Container validation: Rebuild and run Docker images
  4. Smoke testing: Verify these specific areas:
    • BinaryFormatter usage (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 TargetFramework and package references to 9.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.json SDK 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →