Migrating from .NET Framework: what actually breaks, by version
.NET Framework (the older Microsoft software platform many business applications were built on) runs in production at most of the mid-market shops we work with, and most of them are thinking about modernization. The Microsoft answer to "should we modernize" is "yes, move to .NET 10" -- the current, actively developed replacement. The Microsoft answer to "how" is a series of porting guides that range from very useful to nearly empty.
This page covers what we see when teams actually attempt the move. Each entry covers one .NET Framework version: where it's commonly found in production, what's structurally different about it, what specifically breaks during modernization, and what realistic migration paths look like. The last entry covers modern .NET (Core, 5, 6, 7, 8, 9, 10) and explains why moving between those versions is structurally different from moving off Framework.
The version is only one axis of migration risk. What breaks by application type -- Web Forms, WCF, WinForms, and the rest -- is a separate axis, covered in the companion guide what breaks by .NET application type. Read the two together: this page tells you what your Framework version costs you; that one tells you whether your applications are ports or rewrites.
We don't link to Microsoft's per-version migration documentation from each entry because the quality is uneven. The master overview is at learn.microsoft.com/dotnet/core/porting ↗. Read what you find there alongside what we publish here. They serve different purposes.
.NET Framework 3.5
3.5
The version that should have been retired a decade ago and wasn't.
.NET Framework 3.5 was released in 2007 and is the version most commonly found in production at organizations that have a "we don't touch the legacy systems" policy. It's the version that runs the EDI integration (the automated data exchange that moves orders and invoices between companies) nobody understands, the WCF service (Microsoft's older technology for connecting applications to each other) that connects to the mainframe, the WinForms application accounting uses every day. The team is afraid to upgrade it because the original developers are gone and the test coverage is "we'll find out when something breaks."
The technical reality is that .NET Framework 3.5 sits on top of .NET Framework 2.0's CLR. Anything built for 2.0 also runs on 3.5 (not the other way round): 2.0, 3.0, and 3.5 share one runtime, and 3.5 only adds libraries on top. This is sometimes used as an argument that "we can just upgrade to 4.x and it'll keep working." Sometimes that's true. Often it isn't. The 3.5-to-4.x boundary introduces breaking changes in things like serialization behavior, ASP.NET request validation, and several BCL APIs that didn't survive the major-version jump.
The migration from 3.5 to modern .NET is functionally a rewrite for anything non-trivial. The application predates async/await, predates the Task Parallel Library in its modern form, predates the dependency injection patterns that modern .NET assumes -- the building blocks the new platform takes for granted simply aren't there to reuse. It probably uses LINQ to SQL or Entity Framework 3.5, both of which require migration to a different data access stack. WCF server-side hosting doesn't exist in modern .NET; services move to CoreWCF (a Microsoft-supported port -- though the older the bindings, the fewer of them it covers) or get replaced with gRPC or REST endpoints. If the application uses Web Forms (Microsoft's older way of building web pages, also absent from modern .NET), the Web Forms component is a rewrite, not a port.
Realistic migration paths from 3.5 fall into two categories. The first is the staged rewrite: rebuild the application incrementally on modern .NET while running 3.5 in parallel, using the Strangler Fig pattern (replacing the old system piece by piece while it keeps running, rather than all at once) to migrate functionality piece by piece. This is the right answer for most non-trivial 3.5 applications, and it takes 12 to 24 months for a typical mid-market application. The second is the lift-and-shift-then-modernize approach: get the 3.5 application running on a supported infrastructure first (Windows Server 2022 with the .NET Framework 3.5 feature enabled, or retargeted onto the 4.8 runtime, where applicable), then plan modernization separately. This buys time without solving the problem.
Common patterns we see
The application has at least one COM dependency (a piece of older Windows software the application relies on to work) that was set up in 2009 and nobody documented. The COM component is registered on the production server through a manual procedure that exists in a wiki page someone wrote in 2012.
The WCF endpoints use wsHttpBinding with custom certificate authentication that was implemented by an engineer who left the company. Nobody on the current team can explain how the authentication works, only that it works.
The build process requires Visual Studio 2010 or 2013 because the project files reference features that newer versions handle differently. Migrating the project files to current Visual Studio is its own week of work.
If you're running this: The first task isn't planning the modernization. It's reconstructing what the application actually does. Most 3.5 applications have undocumented behavior that surfaces during migration testing rather than during planning. The risk of staying is that the institutional knowledge required to migrate it ages out of the company every year you wait, and at some point the migration becomes a reverse-engineering project before it can become an engineering project.
.NET Framework 4.5
4.5
The first version of "modern" Framework. The line where async/await arrived.
.NET Framework 4.5 was released in 2012 and is the version where Framework started looking like its modern self. It introduced async/await, the modern Task-based asynchronous pattern, and significant improvements to ASP.NET MVC. Applications built fresh on 4.5 generally look more like modern .NET code than applications built on earlier versions, which makes the migration easier.
The structural advantage of 4.5 is that the codebase already uses async/await, modern collections, and patterns that modern .NET expects. The structural disadvantage is that 4.5 reached end of life in 2016 and its sub-version 4.5.2 reached end of life in 2022. Any application still running on 4.5 today is on an unsupported version of a frozen framework and has been for years. The first migration step is usually upgrading to 4.8 to get back into the supported lifecycle, then planning the modern .NET move from there.
The application stack in the 4.5 era was Entity Framework 5 or 6, ASP.NET MVC 4 or 5, Web API 2, and SignalR 1 or 2. Each of these has a modern .NET replacement that requires migration work: EF Core, ASP.NET Core MVC, ASP.NET Core Web API, ASP.NET Core SignalR. The migration paths are well-documented and the conceptual model carries over, but the API surface is different enough that you can't simply re-target the project and rebuild. Method signatures change, configuration moves from XML to code or appsettings.json, and dependency injection becomes mandatory rather than optional.
If the application uses Web Forms, the same problem appears here as in 3.5: Web Forms doesn't exist in modern .NET, and the migration is a rewrite, while ASP.NET MVC and Web API applications port more cleanly. Which application types are ports and which are rewrites is its own axis -- the application-type guide covers it in full.
Common patterns we see
A team we saw on 4.5 upgraded in 2014 and considered the modernization complete. They've been running it for twelve years without touching the framework version. The application has accumulated significant technical debt during that time that has nothing to do with the Framework version itself.
The async/await usage is uneven. Code written before 2014 is mostly synchronous with .Wait() and .Result calls scattered through it. Code written after 2014 is mostly async. The deadlock potential at the boundary -- where old and new async styles meet -- can freeze the application under heavy load. It's the kind of thing that surfaces under production load, not in a demo.
If you're running this: Get to 4.8 first to restore long-term support. Then plan the modern .NET move on a real timeline. The 4.5-to-4.8 step is usually painless and gives you breathing room. The risk of not taking that step is that 4.5 has been out of support since 2022, which means any auditor, cyber carrier, or PE technical due diligence review that touches the application surfaces this as a finding. The framework version becomes the lead item in the report whether or not it should be.
.NET Framework 4.6.2
4.6.2
The version with a specific 2027 end date because Microsoft tied it to Windows Server 2016.
.NET Framework 4.6.2 was released in 2016 and is the version that gets the most precise lifecycle treatment of any Framework version on this page. The other 4.x versions follow the underlying Windows OS lifecycle and have no standalone end date. 4.6.2 has a specific date: January 12, 2027, the same day Windows Server 2016 reaches end of extended support. Microsoft tied them together.
The technical content of 4.6.2 is mostly incremental improvements over 4.5 and 4.6: better cryptography support, better TLS handling, better debugging tools. None of this matters for the migration conversation. What matters is that 4.6.2 sits in an awkward middle position. It's not so old that it's unsupported (yet), and it's not so new that it has the long-term lifecycle of 4.7.2 or 4.8. Teams on 4.6.2 sometimes assume they're "current enough" because the version is supported, then discover they need to plan a Framework upgrade alongside the larger modernization effort.
The realistic migration paths from 4.6.2 are the same as from 4.5: upgrade to 4.8 first to restore long-term support, then plan modern .NET separately. The 4.6.2-to-4.8 step is typically straightforward, with the same caveats about TLS configuration and assembly binding redirects that appear at any Framework minor-version boundary. The bigger conversation is the modern .NET move, which has the same shape it does for any other 4.x version.
Common patterns we see
The application was upgraded from 4.5 to 4.6.2 in 2017 to get TLS 1.2 support for a PCI compliance audit. Nothing else changed. The version bump was a compliance checkbox, not a modernization step.
The team isn't aware that 4.6.2 has a specific 2027 end date and assumes they have until "whenever Windows reaches end of life." They have until January 2027.
If you're running this: You have a specific deadline (January 12, 2027) and most teams on 4.6.2 don't know it exists. Use the deadline as the forcing function for the broader modernization conversation rather than the trigger for a minimal 4.8 upgrade. The risk of treating it as a Framework version bump is that you pay for the upgrade work twice: once to get to 4.8, once to get to modern .NET. The price of using the deadline to drive real modernization is the budget conversation -- easier now than it will be once the deadline is six months out.
.NET Framework 4.7.2
4.7.2
The version most mid-market shops are actually running. The "we're current" version that isn't.
.NET Framework 4.7.2 was released in 2018 and is the Framework version we meet most often in mid-market production. Teams running 4.7.2 typically describe themselves as "on the current Framework" because they're not on the obviously old versions. They're not wrong, exactly. They're also not on a version that's getting any meaningful improvements.
4.7 brought in-box ValueTuple support; 4.7.2 added dependency injection helpers and improved ASP.NET features. The bigger story is what it didn't change: the underlying CLR architecture, the threading model, the assembly loading behavior. These haven't meaningfully changed across the 4.x line and won't change without a move to modern .NET. Teams on 4.7.2 looking for "what's new" generally find a list of small improvements that doesn't justify a Framework upgrade on its own.
The migration paths from 4.7.2 to modern .NET are among the cleanest of any Framework version on this page. The codebase is recent enough to use modern C# features, the patterns are close enough to modern .NET that the port doesn't require fundamental rearchitecting, and the supporting libraries are usually current enough that their modern .NET equivalents are well-documented. The migration is still months, but it's not the years that 3.5 or Web Forms applications require.
The honest version of the 4.7.2 conversation: this is the version where "we're not in a hurry" feels reasonable. The application works, it's supported (via the Windows OS lifecycle), and the team has more pressing problems than a Framework migration. This is also the version where the gap between "running fine on Framework" and "actually modern" is widening fastest. Modern .NET is getting AOT compilation (faster startup and a smaller footprint, which lowers cloud hosting costs), native interop improvements, and AI integration patterns that Framework will never get. The team is going to migrate eventually. Migrating from 4.7.2 next year will be more expensive than migrating this year.
Common patterns we see
The application uses System.Web.Mvc 5.x and was last meaningfully updated in 2020. Nobody is asking for new features, so the team doesn't have a forcing function to modernize.
The team is on 4.7.2 because they upgraded once, three years ago, after a different team went through a difficult 4.5-to-4.7.2 migration. They don't want to repeat that experience for a 4.8 step that "doesn't get them anywhere."
The CI/CD pipeline is configured for 4.7.2 specifically. The build agents have the .NET Framework 4.7.2 Developer Pack installed. Updating to 4.8 requires touching the build infrastructure, which is owned by a different team.
If you're running this: The migration from 4.7.2 to modern .NET is easier today than it will be in twelve months. The risk of waiting is that the gap between Framework and modern .NET widens every release cycle, and 4.7.2 looks current today and dated in 2027. Teams that postpone modernization on 4.7.2 are not buying time. They are pricing in a more expensive migration that nobody is budgeting for yet.
.NET Framework 4.8 (and 4.8.1)
4.8
The terminal Framework version. The one Microsoft will keep patching but won't improve.
.NET Framework 4.8 was released in 2019. .NET Framework 4.8.1 was released in 2022. Both will continue to ship with future Windows versions for the foreseeable future. There is no .NET Framework 4.9 coming. There is no .NET Framework 5. Microsoft has been explicit that 4.8.1 is the final release of the Framework line and that all future innovation will happen in modern .NET.
This creates an unusual situation. The version is "supported" indefinitely in the narrow sense that Microsoft will issue critical security patches. The version is also frozen in the meaningful sense that no new features, no performance improvements, no new APIs, and no modern C# features will be added. Teams that hear "supported indefinitely" sometimes interpret it as "we have indefinite time to modernize." Teams that hear "frozen" understand that the gap between Framework and modern .NET will widen every year the team stays.
The migration from 4.8 to modern .NET is the cleanest version of the Framework-to-modern story. The code is recent. The patterns are familiar. The libraries are current. Most of the friction in a 4.8 migration comes from things that aren't really about 4.8 at all: legacy WCF services, embedded Web Forms components, COM dependencies, integration with on-premises SQL Server using older drivers. The Framework version is incidental. The architecture is the problem.
Whether each of those is a port or a rewrite -- the Web Forms portal, the server-side WCF, the COM dependency -- is the application-type question, not a version question, and it's covered in what breaks by .NET application type. One language note that page doesn't cover: if the application is VB.NET, the move is technically possible (modern .NET supports VB.NET) but in practice often becomes a C# rewrite, because VB.NET is in maintenance mode with no new language features.
The honest 4.8 conversation: this is the version where staying is least defensible and most common. Teams on 4.8 know the modernization is coming and have been postponing it for two or three years. The postponement is usually rational on a quarter-by-quarter basis (the migration is a real cost, the application works, no immediate forcing function). The postponement is rarely rational over a five-year horizon.
Common patterns we see
The application was migrated from an earlier Framework version to 4.8 in 2020 or 2021, with the explicit goal of "getting to current" and the implicit assumption that 4.8 was a destination rather than a stopover. The team is now in year five or six of treating 4.8 as the destination.
The team has read the modern .NET migration documentation and built a proof-of-concept port of a small component. The proof-of-concept worked. The proof-of-concept has not been productionized because nobody can find time for the full migration.
The team says there is no Web Forms; a Web Forms admin portal turns up anyway. The admin portal is rarely used and not user-facing. It still has to be rebuilt as part of any modern .NET migration.
If you're running this: The "supported indefinitely" framing is technically accurate and strategically misleading. Stay on 4.8 because the application is stable, not because you have time. The risk is the widening capability gap between what your application can do and what modern .NET applications can do. Three years from now, "we're on 4.8" will read the same way "we're on 4.5" reads today. The clock is running. Microsoft just isn't showing it to you.
Modern .NET (Core, 5, 6, 7, 8, 9, 10)
Modern
The other side of the migration. Different rules, different cadence, different conversation.
Modern .NET is everything Microsoft ships under the .NET name that isn't .NET Framework. .NET Core was the original cross-platform reboot starting in 2016. Starting in 2020, Microsoft unified everything under the .NET 5 name and has shipped a major version every November since. .NET 6, 7, 8, 9, and 10 are all part of the same modern .NET line. They run on the same runtime architecture, support the same C# language features (within each version's release window), and follow the same Microsoft support cadence.
The fundamental difference between modern .NET and Framework is that modern .NET is actually being developed. New language features, new runtime capabilities, performance improvements. Every November release brings real changes that affect what applications can do. The Framework line is frozen. The modern .NET line is the active development platform.
The support cadence for modern .NET is shorter than Framework. LTS versions (Long-Term Support -- even-numbered: .NET 6, 8, 10) get three years of security patches and fixes. STS versions (Standard-Term Support -- odd-numbered: .NET 7, 9) are supported for a shorter window: 18 months through .NET 7, then two years (24 months) from .NET 9 on, when Microsoft extended the STS window. That means .NET 9, released in November 2024, reaches end of support in November 2026, the same date as .NET 8 LTS. .NET 10 LTS, released November 2025, is supported through November 2028. The companion page Microsoft .NET release cycle and the LTS upgrade trap covers this cadence and the trap teams fall into.
This shorter support cadence makes the migration story very different from Framework. Moving from .NET 8 to .NET 10 is a routine version-to-version upgrade for a healthy application: days to weeks of work. The runtime is the same architecture, the C# patterns are compatible, the libraries are mostly source-compatible. The biggest friction is usually third-party library updates and the handful of breaking changes Microsoft documents in the release notes. This is structurally different from the Framework-to-modern migration, which is months to years.
The trap teams fall into when planning modern .NET adoption: assuming the upgrade cadence will feel like Framework. Teams sometimes try to skip versions ("we'll go from .NET 6 to .NET 10 in one jump") and discover the intermediate releases had important changes they need to absorb. Other teams try to stay on a single LTS version for the whole three years and then find themselves out of support before they've planned the next upgrade. The successful pattern is treating the November release cycle as a regular maintenance rhythm, not a series of major migrations.
Common patterns we see
The team migrated to .NET Core 3.1 in 2020, then to .NET 6 in 2022, then stopped paying attention because "we're on modern .NET now." They're on .NET 6, which reached end of life in November 2024. They didn't notice.
The team is on .NET 8 LTS and considers themselves current. They are. They also have until November 10, 2026, before .NET 8 reaches end of support, and they haven't planned the .NET 10 upgrade yet.
The team mixed and matched: some services on .NET 6, some on .NET 7, some on .NET 8. Each was the current version when that service was built. The team now needs to consolidate, and the consolidation is more work than upgrading each service separately would have been.
If you're running this: Treat the modern .NET upgrade rhythm as a continuous maintenance activity, not a series of discrete migrations. The applications that handle modern .NET well are the ones whose teams budget for a small upgrade every one to two years. The risk of treating modern .NET like Framework is that the small upgrades you skipped accumulate into a large upgrade you can't schedule. Skip two releases and the catch-up usually runs months, not days; skip four and it stops fitting in a single quarter and ends up postponed indefinitely. The pattern repeats until the team finds itself on an out-of-support version, surprised.
The version is the easy part. What surrounds it is not.
Modernization Shield goes beyond the target framework to identify Web Forms, WCF, COM, package, integration, and runtime dependencies that decide whether the work is an upgrade, a port, or a rewrite.
The SocialGoal sample Modernization Risk Report shows how those blockers are documented, traced to evidence, and ordered for a migration.
This page reflects patterns observed across the mid-market .NET systems we assess. Patterns will shift as Microsoft ships new modern .NET releases and as Framework cohorts age. If a claim on this page contradicts your direct experience, we'd rather hear about it than ship something inaccurate. Email info@evincia.co.