# How to Migrate .NET Projects Between Versions: A Complete .NET 8 to .NET 9 Upgrade Guide

> Easily migrate .NET projects between versions like .NET 8 to .NET 9 with the official dotnet-upgrade skill. Discover a six-phase workflow for a smooth upgrade process.

- Repository: [.NET Platform/skills](https://github.com/dotnet/skills)
- Tags: migration-guide
- Published: 2026-07-07

---

**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`](https://github.com/dotnet/skills/blob/main/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:

```xml
<!-- 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:

```bash
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<T> 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`](https://github.com/dotnet/skills/blob/main/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`](https://github.com/dotnet/skills/blob/main/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`](https://github.com/dotnet/skills/blob/main/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`](https://github.com/dotnet/skills/blob/main/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:

```dockerfile
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`](https://github.com/dotnet/skills/blob/main/global.json) to specify the .NET 9 SDK:

```json
{
  "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:

```bash
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`](https://github.com/dotnet/skills/blob/main/plugins/dotnet-upgrade/skills/migrate-dotnet8-to-dotnet9/SKILL.md) | Primary migration workflow |
| [`references/csharp-compiler-dotnet8to9.md`](https://github.com/dotnet/skills/blob/main/references/csharp-compiler-dotnet8to9.md) | C# 13 compiler breaking changes |

| [`references/core-libraries-dotnet8to9.md`](https://github.com/dotnet/skills/blob/main/references/core-libraries-dotnet8to9.md) | Core library API changes and SYSLIB warnings |
| [`references/serialization-networking-dotnet8to9.md`](https://github.com/dotnet/skills/blob/main/references/serialization-networking-dotnet8to9.md) | BinaryFormatter, HttpClient, and networking changes |
| [`references/aspnet-core-dotnet8to9.md`](https://github.com/dotnet/skills/blob/main/references/aspnet-core-dotnet8to9.md) | ASP.NET Core DI and configuration changes |
| [`references/efcore-dotnet8to9.md`](https://github.com/dotnet/skills/blob/main/references/efcore-dotnet8to9.md) | Entity Framework Core migration changes |
| [`references/containers-interop-dotnet8to9.md`](https://github.com/dotnet/skills/blob/main/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`](https://github.com/dotnet/skills/blob/main/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`](https://github.com/dotnet/skills/blob/main/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.