A new major .NET release arrives, and the team chat immediately splits into two camps: the people who want to upgrade this week because it is new, and the people who want to wait two years because change is scary. Both instincts are wrong as stated. Having moved production payment systems across several .NET versions, sometimes smoothly and once memorably not, I have settled into a pragmatic middle path that avoids both reckless early adoption and the slow rot of falling too far behind.
Upgrade for reasons, not for novelty
"It is the latest version" is not, by itself, a reason to upgrade a production system that moves people's money. Novelty is not a business case. The reasons that genuinely justify the effort and the risk are concrete and specific.
- Performance gains that actually move your real bottleneck, not a benchmark you do not care about.
- Security fixes and a supported lifecycle you cannot responsibly ignore.
- Features that remove genuine, daily friction your team currently fights.
- Staying close enough to current that the eventual, unavoidable jump is not enormous.
That last reason quietly matters the most. The teams in real trouble are the ones several major versions behind, facing a migration so large and so risky that no one wants to be the person who starts it, so no one ever does.
Mind the LTS rhythm
Microsoft alternates long-term-support releases with standard-term ones, and that rhythm should shape your strategy directly. For the core systems that underpin a payments business, I weight LTS releases heavily, because the longer support window is worth considerably more to me than the newest features. Stability and a long patch horizon are features, in this context, and arguably the most important ones.
For the service that actually moves money, boring and well-supported beats new and exciting every single time, without exception.
I will happily adopt a standard-term release in less critical, lower-blast-radius services to stay current and let the team learn the new capabilities. But the load-bearing systems track LTS, deliberately and unapologetically.
Upgrade in layers, not in one leap
I never upgrade everything at once, because the runtime, the framework, and the third-party dependencies each carry their own independent risk, and a big-bang upgrade makes it impossible to tell which of them broke the thing that is now broken. When five things changed simultaneously, debugging the regression is a nightmare of elimination.
Move in layers instead, with a strong test suite catching regressions at each discrete step. Done this way, the upgrade becomes a series of small, individually reversible moves rather than one large, terrifying, all-or-nothing leap. If something breaks, you know exactly which layer did it, because it is the only thing you just changed.
Test the things unit tests miss
Runtime upgrades can shift behavior in subtle ways that unit tests will never catch: serialization edge cases, datetime and timezone handling, garbage-collection and performance characteristics under real load. These are exactly the surprises that pass every test in CI and then bite you in production at peak.
So before a production cutover I run the new version against production-like traffic, ideally a shadow or replay of real load, and watch the metrics that actually matter for the business. The genuinely dangerous surprises almost always live in the gap between "it compiles and the tests are green" and "it behaves identically at production scale."
Have a tested way back
However thorough the testing and however confident the team, I never deploy a runtime upgrade without a tested, rehearsed rollback path. Confidence is an emotion, not a deployment strategy, and the new release will eventually surprise everyone, including the people who built it.
The ability to revert quickly and cleanly is precisely what lets you upgrade boldly, because it bounds the cost of being wrong. A reversible mistake is a learning experience; an irreversible one in a payment system is an incident report and a very long night. That asymmetry is why the rollback plan is not optional.
Conclusion: current, deliberately
The goal is to stay current deliberately rather than either chasing every release or hiding from all of them. Upgrade when there is a real reason, weight LTS for the systems that matter, move in reversible layers, test the things that do not show up in unit tests, and always keep a tested way back. Do that consistently and staying on a modern, supported .NET becomes a sustainable, almost boring habit, rather than the terrifying once-every-three-years migration that the teams who avoided it eventually cannot avoid any longer.
