Choosing Between Fixed and Floating NuGet Versions in .NET Solutions
Guidance on selecting fixed or floating NuGet versions, using Central Package Management and lock files for reproducible builds.
20 Aug 2025, 19:54 UTC

Decision and constraints
When managing dependencies in a .NET solution you must decide how to specify NuGet package versions. The decision influences build reproducibility, maintenance effort, and the risk of unexpected breaking changes. Constraints often include:
- Need for deterministic CI/CD builds.
- Desire to reduce manual version bumps for internally maintained libraries.
- Requirement that all projects in a solution use the same version of a shared package.
- Availability of a lock‑file mechanism to guarantee identical dependency graphs across developers and build agents.
Supported options
| Option | How it is expressed | Typical use case | Advantages | Drawbacks |
|---|---|---|---|---|
| Fixed version | <PackageReference Include="MyLib" Version="1.2.3" /> |
Libraries that are consumed by many services where stability is critical. | Deterministic builds; prevents surprise updates; works with lock files. | Requires manual version bumps; can lead to version drift if not coordinated. |
| Floating version (patch) | <PackageReference Include="MyLib" Version="1.2.*" /> |
Internal libraries where you want the latest bug‑fixes automatically. | Reduces manual maintenance; receives patch updates without CI changes. | Can produce non‑deterministic builds; a new patch might introduce a breaking change. |
| Floating version (minor) | <PackageReference Include="MyLib" Version="1.*" /> |
Rapidly evolving internal packages where you accept new features. | Gets the latest minor and patch updates. | Highest risk of breaking changes; makes reproducibility harder. |
| Central Package Management (CPM) | Defined in Directory.Packages.props with <PackageVersion> elements. |
Solutions with many projects that should share the same version set. | Single source of truth; simplifies version updates; works with both fixed and floating syntax. | Requires .NET SDK 6.0+; all projects must import the props file. |
| Lock file | Generated packages.lock.json when restoring with fixed versions (or CPM with fixed versions). |
Ensuring every developer and CI agent gets exactly the same package set. | Guarantees identical dependency graphs; can be committed to source control. | Only effective when all direct dependencies are fixed; floating versions bypass the lock. |
Trade‑offs
The core tension is between Stability and Reproducibility (fixed versions + lock file) and Agility and Low Maintenance (floating versions). If your pipeline must produce bit‑identical artifacts for every commit, choose fixed versions and enable CPM so that a single Directory.Packages.props controls the version. If you are willing to accept occasional rebuilds to stay current with bug‑fixes in an internal library, a floating patch version (1.2.*) can reduce the overhead of version bumps, but you must accept that the same source may produce different binaries over time.
Concrete implementation
Using fixed versions with Central Package Management
Create a Directory.Packages.props file at the solution root:
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
<PackageVersion Include="MyCompany.CoreLib" Version="2.1.0" />
</ItemGroup>
</Project>
Each project then references the package without a version:
<PackageReference Include="Newtonsoft.Json" />
<PackageReference Include="MyCompany.CoreLib" />
After editing the props file, run a restore to generate the lock file:
dotnet restore
The lock file obj/project.assets.json (or the solution‑wide packages.lock.json when using the --locked-mode flag) will contain the exact resolved versions.
Using floating versions for an internal library
If you want to automatically receive the latest patch of an internal library, you can keep the version floating in the project file:
<PackageReference Include="MyCompany.InternalUtil" Version="4.5.*" />
When using CPM, you can still specify a floating version in Directory.Packages.props:
<PackageVersion Include="MyCompany.InternalUtil" Version="4.5.*" />
Note that with a floating version the lock file will not capture a fixed value; each restore may resolve a different patch.
Validation steps
Run the restore from a command prompt with sufficient access to the solution folder (no elevated privileges required):
dotnet restoreInspect the generated assets file to confirm the resolved version. For a fixed version you should see an exact match, e.g.:
For a floating version you will see a version like<dependency> <id>MyCompany.CoreLib</id> <version>2.1.0</version> </dependency>4.5.3(the latest patch at restore time).List the resolved packages to verify the version across projects:
The output should show the same version for every project that references the package when using fixed versions/CPM.dotnet list packageTo test the effect of a version bump, change the version in
Directory.Packages.props(or the floating range) and repeat steps 1‑3. Observe that the assets file updates accordingly.
Limitations and practical checks
- Floating versions defeat the purpose of a lock file; if you need reproducibility, avoid them for production‑facing dependencies.
- CPM requires .NET SDK 6.0 or later. Older SDKs will ignore the
Directory.Packages.propsfile and produce a warning. - When using floating versions, consider implementing a version‑policy check in your CI pipeline that fails if a new patch introduces a breaking change (e.g., by running API compatibility tests).
- Always commit
Directory.Packages.propsand, if you use fixed versions, the generatedpackages.lock.jsonto source control so that all agents restore the same set.
Rollback
If you decide to revert a versioning change:
- Edit
Directory.Packages.props(or the individual.csprojfiles) to restore the previous version syntax. - Delete the existing
obj/project.assets.jsonandpackages.lock.jsonfiles to force a clean restore. - Run
dotnet restoreagain and verify the resolved versions match the earlier state.
This operation only changes restore state; it does not alter source code, so it is safe to repeat as needed.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.