NuGet's Lowest-Version Selection Is Not a Bug
NuGet 6.2+ resolves version ranges by selecting the lowest matching stable version that satisfies all constraints, not the highest. This is intentional design, not a conflict with expectations.
Practical Implications for Small Application Upgrades
During a controlled upgrade, this behavior means:
- Stability over recency: Dependencies receive the oldest version that works, reducing exposure to newer bugs.
- Explicit versions recommended: If your upgrade requires specific features or fixes, pin exact versions in
PackageReference to avoid receiving older builds. - Transitive dependency consistency: A direct reference to
[1.0,) may resolve to 1.0.0 even if 1.5.2 exists, unless a transitive dependency pulls a higher version.
When Lowest-Match Is Guaranteed
This behavior is deterministic across all NuGet 6.2+ environments when using PackageReference:
- MSBuild integration: Resolution occurs during restore, using the same algorithm in Visual Studio,
dotnet restore, and CI/CD pipelines. - No built-in highest-wins option: Without explicit version pinning or
CentralPackageTransitivePinning, lowest-match applies universally. - Lock files enforce reproducibility:
.nuget.gpkg or packages.lock.json ensures identical resolution across machines.
Verification Steps
Run these commands to confirm resolved versions:
dotnet list package --include-transitive
cat .nuget/packages.lock.json | grep -A2 "resolved"
Key Takeaway
This is not a bug to fix but a design choice favoring stability. For upgrades requiring newer versions, specify them explicitly rather than relying on range defaults.