Managing .NET SDK Versions with global.json
If you've ever had a build work on your machine but fail on a colleague's — or in CI — the cause is often a mismatched .NET SDK version. global.json solves this by pinning the SDK version for a repository, ensuring every developer and build agent uses the same toolchain.
The basics
Create a global.json file in your repository root:
{
"sdk": {
"version": "8.0.404"
}
}
When you run dotnet build (or any dotnet CLI command) in a directory that contains a global.json — or any ancestor directory — the CLI uses the specified SDK version. If that version isn't installed, the command fails with a clear error message rather than silently using whatever is available.
Generate one interactively:
dotnet new globaljson --sdk-version 8.0.404
Roll-forward policies
Strict version pinning is sometimes too rigid. The rollForward property controls how the CLI selects an SDK when the exact version isn't available:
{
"sdk": {
"version": "8.0.400",
"rollForward": "latestPatch"
}
}
The available policies, from most restrictive to most permissive:
| Policy | Behaviour |
|---|---|
disable |
Exact match only. Fails if the version isn't installed. |
patch |
Uses the requested version or the latest patch in the same major.minor.patch-band. Default behaviour. |
feature |
Uses the latest patch in the same major.minor with a feature band >= the requested one. |
latestPatch |
Uses the latest patch in the same major.minor.feature-band. |
latestFeature |
Uses the latest feature band and patch in the same major.minor. |
latestMajor |
Uses any installed SDK >= the requested version. |
latestMinor |
Uses the latest minor, feature band, and patch in the same major. |
For most teams, latestPatch strikes the right balance — you get security patches without unexpected feature changes.
Understanding SDK versioning
The .NET SDK version format is major.minor.feature-band-patch:
8.0.404
│ │ │││
│ │ │││
│ │ ││└─ patch (04)
│ │ │└── patch (04) -- combined as feature band patch
│ │ └─── feature band (4)
│ └───── minor (0)
└─────── major (8)
The feature band represents a quarterly release cadence. SDK 8.0.100 shipped with .NET 8 GA; 8.0.200 came with Visual Studio 17.9; 8.0.300 with Visual Studio 17.10; and so on.
This distinction matters because rollForward: "patch" won't jump from 8.0.100 to 8.0.200 — those are different feature bands.
Multiple SDKs on one machine
You can install multiple SDK versions side by side. List installed versions:
dotnet --list-sdks
Output:
6.0.428 [C:\Program Files\dotnet\sdk]
8.0.404 [C:\Program Files\dotnet\sdk]
9.0.200 [C:\Program Files\dotnet\sdk]
Without a global.json, the CLI uses the latest installed SDK. This is why a global.json matters — without it, a developer who installs .NET 9 Preview will use preview tooling for a .NET 8 project, which may produce different build artifacts or exhibit different compiler behaviour.
CI/CD integration
In CI pipelines, install the exact SDK version from your global.json. For GitHub Actions:
- uses: actions/setup-dotnet@v4
with:
global-json-file: global.json
This reads the version from your global.json and installs it, ensuring CI matches local development exactly.
For Azure DevOps:
- task: UseDotNet@2
inputs:
useGlobalJson: true
Keeping the SDK current
A stale global.json can block important updates. Establish a routine:
- When a new patch release drops (e.g.,
8.0.404to8.0.405), updateglobal.jsonand test. - When a new feature band ships (e.g.,
8.0.400to8.0.500), update with more care — feature bands occasionally include SDK-level changes. - When a new major version ships, plan a proper upgrade.
Automate this with Dependabot or Renovate — both support global.json as a tracked file.
Additional global.json options
Beyond SDK pinning, global.json can configure MSBuild SDK resolvers:
{
"sdk": {
"version": "8.0.404",
"rollForward": "latestPatch"
},
"msbuild-sdks": {
"Microsoft.Build.Traversal": "4.1.0"
}
}
This is useful for custom MSBuild SDKs used in large repositories for build orchestration.
Wrapping up
A global.json is a small file with an outsized impact on build reliability. Add one to every repository, choose a sensible roll-forward policy, and integrate it into your CI pipeline. It takes two minutes to set up and prevents hours of "works on my machine" debugging.