I often encounter scenarios where I need to maintain existing .NET 8 applications while taking advantage of the latest tooling improvements in .NET 9. This is particularly relevant when working with enhanced language servers like Roslyn that benefit from the newer SDK, even when the application itself targets an older framework.
In this post, I’ll share how to successfully configure your development environment to run .NET 8 applications while leveraging the .NET 9 SDK for improved development tooling.
Modern development environments benefit significantly from the latest language servers and tooling. Roslyn, Microsoft’s .NET compiler platform, provides enhanced IntelliSense, refactoring capabilities, and code analysis features that are continuously improved in newer versions. However, many production applications still target .NET 8 due to stability requirements, compatibility concerns, or organizational upgrade policies.
This creates a common scenario where you want:
The key is to install both .NET 8 and .NET 9 SDKs in your development environment and configure your projects to target .NET 8 while allowing the newer SDK’s tools to enhance your development experience.
For reproducible environments, I use Nix to manage my development setup. Here’s how I configure my shell.nix to include both SDKs:
{ pkgs ? import (fetchTarball "https://channels.nixos.org/nixpkgs-unstable/nixexprs.tar.xz") { } }:
let
# One dotnet host that contains multiple SDKs
dotnetCombined = pkgs.dotnetCorePackages.combinePackages [
pkgs.dotnetCorePackages.sdk_8_0
pkgs.dotnetCorePackages.sdk_9_0
];
in
pkgs.mkShell {
packages = [
dotnetCombined
pkgs.nixd
pkgs.roslyn-ls
];
# Basics that keep Roslyn/LSPs happy and silence telemetry
shellHook = ''
export DOTNET_ROOT=${dotnetCombined}
export PATH="${dotnetCombined}/bin:$PATH"
export DOTNET_CLI_TELEMETRY_OPTOUT=1
# Show what's available so you can confirm both SDKs are there
echo "Available .NET SDKs:"
dotnet --list-sdks
'';
}
This configuration provides:
To ensure your application targets .NET 8 while benefiting from .NET 9 tooling, use a global.json file in your project root:
{
"sdk": {
"version": "8.0.408",
"rollForward": "latestFeature"
}
}
This configuration ensures that:
In your project files, explicitly target .NET 8:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<!-- Other properties -->
</PropertyGroup>
</Project>
By using the .NET 9 SDK’s Roslyn language server, you get:
Your application continues to target .NET 8, ensuring:
This setup makes upgrading easier when you’re ready:
With this configuration, your daily workflow looks like:
# Enter the project directory
cd my-dotnet-project
# The environment automatically activates with both SDKs
# Available .NET SDKs:
# 8.0.408 [/nix/store/.../dotnet-sdk-8.0.408]
# 9.0.100 [/nix/store/.../dotnet-sdk-9.0.100]
# Build and run with .NET 8
dotnet build
dotnet run
# Development tools use .NET 9 features
# Your IDE gets enhanced IntelliSense and refactoring
This approach works well for teams because:
If you encounter version conflicts, explicitly specify the SDK in your build commands:
dotnet build --sdk-version 8.0.408
Running .NET 8 applications with .NET 9 SDK tooling provides the best of both worlds: production stability with modern development capabilities. This approach allows you to maintain existing applications while benefiting from the latest tooling improvements.
The Nix-based configuration ensures reproducibility across environments, making it ideal for team collaboration and CI/CD pipelines. As you plan your migration to .NET 9, this setup provides a smooth transition path with minimal disruption to your current workflow.