Legacy Code Modernization Without a Full Rewrite
Incremental modernization beats full rewrites for most legacy systems.

Legacy code doesn't need a rewrite nearly as often as engineering leaders think. Most of the time it needs modernizing one capability at a time, with the system running the whole way through, and the real skill is knowing where a given system actually sits on that spectrum. Get that judgment wrong and it turns into a $370 million mistake. That's the case this piece is making, and it's worth sitting with before the next rewrite pitch lands on your desk.
Legacy isn't a corner case. A 2025 survey of more than 500 U.S.-based IT professionals found 62% of organizations still run on legacy software systems, and McKinsey puts the number higher for big companies: 70% of Fortune 500 firms operate software over two decades old.
Age isn't really the problem, though. Michael Feathers, in Working Effectively with Legacy Code, defined legacy code as code without tests. Not old code. Code you can't safely change because nothing tells you when you've broken it. Sit with that, because it has a strange implication: a system built last year with zero test coverage is often harder to touch than a twenty-year-old system with a solid test suite behind it. Changeability is the metric. Age is just a proxy that happens to correlate sometimes, and only sometimes.
So here's the actual bind most engineering leaders are in. The system running payroll, or processing claims, or routing freight, can't go offline to get fixed. It also can't stay frozen, because the business around it keeps changing shape. A paper on scalable CI/CD for legacy modernization calls this the "black box" problem: no single person holds knowledge of all the underlying processes anymore, operations run off old manuals, and updates become difficult to implement and even harder to verify. The system works well enough to keep. It doesn't work well enough to grow on.
What technical debt is actually costing, in time and money
Start with the number that reframes the whole conversation. Accenture's 2025 Digital Core report puts the annual cost of technical debt in the U.S. alone at $2.41 trillion, with roughly $1.52 trillion needed just to remediate what's already piled up.
Deloitte's 2026 Global Technology Leadership Study breaks that down at the budget level: technical debt eats 21% to 40% of total IT spending. For every $100 a company spends on IT, up to $40 goes to servicing debt instead of building anything new. In the worst cases, that's nearly half a budget spent standing still.
It shows up in developer time too. Stripe's Developer Coefficient study found developers lose an average of 13.4 hours a week, about 33% of working time, to technical debt issues. A third of every engineering hour on payroll goes toward keeping old code alive rather than shipping anything new.
The Software Improvement Group's State of Software 2026 report puts a number on the upside: moving one system from a delivery-risk maintainability level up to the recommended threshold frees roughly 5.8 full-time-equivalent staff, worth about €870,000 per system per year in recovered capacity. Per system. Multiply that across a dozen legacy applications and the number stops feeling abstract.
There's a newer cost showing up now too: AI adoption. 68% of organizations report legacy systems actively blocking AI adoption, and companies running on legacy infrastructure are 30% more likely to hit delays rolling out AI tools. Old data models and undocumented interfaces don't play well with anything that needs clean, structured input. Nobody designed them for that, so why would they?
Security is the sharper edge of the same problem. IBM has reported the average cost of a breach in a legacy environment at $4.4 million, and 43% of IT professionals named security vulnerabilities as a major legacy concern. When technical debt fails publicly, it fails big: the Wall Street Journal reported that technical debt contributed to 16,900 canceled Southwest Airlines flights during the 2022 holiday season. One overwhelmed crew-scheduling system canceled a holiday for hundreds of thousands of travelers.
None of this is a cost sitting in some hypothetical future. It's already being paid, quietly, out of engineering budgets and operations teams, every single week.
Why a full rewrite is the expensive default, not the obvious answer
Given numbers like that, the instinct to just start over makes emotional sense. It rarely makes financial sense, and the data backs that up plainly. Pegasystems' late-2025 research found the average global enterprise wastes more than $370 million a year on inefficient legacy modernization, much of it from projects that ran too long, cost too much, and disrupted more of the business than anyone budgeted for.
Softjourn's guidance on this is blunt: rewriting a legacy codebase from scratch isn't recommended. It takes enormous time and effort, and by the time it ships, it's often already behind again. The organization ends up roughly where it started, just poorer.
Joel Spolsky made the underlying point back in 2000, and it hasn't aged a day: it's harder to read code than to write it. An engineer opens an unfamiliar codebase, sees what looks like a mess of odd branches and special cases, and concludes the whole thing should get torched and rebuilt. What that engineer usually can't see is that most of those strange branches are scar tissue. They're where real bugs got fixed, for real customers, over years of production use. A rewrite doesn't inherit any of that. It has to relearn every edge case the hard way, by failing in front of users again, one at a time. Every unusual transaction path, every "what happens when someone orders a negative quantity on a Tuesday" case, gets rediscovered from scratch.
There's a quieter incentive problem too, worth naming plainly: most vendors who rank highly for "legacy modernization" sell exactly one kind of answer. A low-code platform recommends low code. A cloud hosting company recommends moving everything to its cloud. A consultancy billing by the hour benefits, structurally, from a three-year rebuild engagement rather than a six-month fix. None of that makes the advice wrong automatically. But it should make you ask who's paying for the recommendation, and why it always seems to land on the priciest option on the menu.
A rewrite pitch is the wrong default, and the case against it rests on more than sentiment. It's arithmetic: most systems being pitched a full rewrite don't need one. So here's a useful filter for any founder or CTO sitting through that pitch: ask what specifically about the system makes incremental modernization impossible. A vendor with a real answer names something concrete, a file, a table, a hard architectural constraint. A vendor without one talks in generalities about the system being "too old" or "too fragile" to touch carefully. That vagueness is the tell.
None of this argues against ever rewriting anything. It argues for understanding the full range of options before reaching for the most expensive, riskiest one on the shelf.
The modernization spectrum: what the AWS 7 Rs framework actually describes
AWS built a framework years ago that's become the shared vocabulary for this entire space, whether the people using it know the source or not: the 7 Rs. Worth knowing, because the word "modernization" on its own hides which strategy is actually being sold underneath it.
The spectrum runs from least disruptive to most:
- Retain or revisit. Leave the system running as is, fix the one specific thing causing pain. Right call when the system's stable overall and the problem is narrow.
- Rehost, often called "lift and shift." Move the application to better infrastructure, usually the cloud, without touching the core code. Fast, lower risk, useful when the bottleneck is aging hardware rather than application logic. Maintenance costs typically drop within months of getting off old on-premise servers.
- Replatform. Same idea as rehosting, but with targeted optimizations along the way. More benefit, and it still keeps existing logic intact.
- Refactor, or re-architect. Restructure the existing code in place, incrementally, to cut complexity and build cleaner integration points. The system keeps running the entire time.
- Repurchase. Replace the legacy system with a commercial or SaaS product that does the same job.
- Retire. Shut down systems that no longer serve a business purpose. Sometimes the best modernization move is just turning something off.
- Replace, meaning the full rewrite. The far end of the spectrum, and the most expensive by a wide margin.
AWS's own guidance to enterprises going through large migrations is worth sitting with here: refactor only when no other migration strategy is acceptable. It's described as the most complex and costly of the seven, and not the opening move.
Notice something about that list: "modernization" applies to every row on it. Rehosting is modernization. Retiring a dead system is modernization. So when a vendor jumps straight to "replace" without walking through the other six options first, that's worth questioning. They haven't made a case. They've skipped to their preferred conclusion, and it's usually the one with the biggest invoice attached.
One step has to come before choosing a row at all: application rationalization. Inventory every system in play, score each one on business value, maintenance cost, technical fitness, and risk, then match each system to its own path on the spectrum. One answer for the whole portfolio is almost never right.
What characterization tests do before any other change is made
Feathers' definition, code without tests, also hands over the first move: write tests that document current behavior before changing anything at all.
These are called characterization tests, and they do something subtly different from normal tests. They don't assert what the system should do. They record what it actually does, quirks included, even the weird ones nobody left at the company can explain anymore.
Why does this matter so much? Without a test harness, any change to legacy code is a change made blind. Refactoring depends on it. Rehosting depends on it. Strangler fig displacement, covered next, depends on it entirely. None of these moves work without a reliable way to verify the moved or altered code still behaves the way it used to.
This is also where the black box problem gets solved in practice, not just diagnosed on a whiteboard. Without that harness, even a small change to a legacy system can require extensive tracing of hidden dependencies before anyone touches a line. Characterization tests pull that hidden knowledge out of people's heads and into something written down, something that can't walk out the door when the one engineer who understood the billing module quits.
There's a parallel move worth pairing with this: API wrapping. Wrapping the legacy system in a stable API or event interface lets new features get built around it without touching the fragile core underneath. It protects everything downstream from the legacy system's outdated interfaces, and it buys real time to make deeper changes later instead of getting forced into them immediately.
Both moves, characterization tests and API wrapping, build the safety net first. Neither one is the modernization itself. They're what makes modernization possible without a blindfold on, and skipping either one is how a "careful" refactor turns into an outage nobody saw coming.
How the strangler fig pattern displaces a legacy system without taking it offline
Martin Fowler, Chief Scientist at Thoughtworks, coined this term in 2004, and the biological metaphor is exact. A strangler fig starts as a seed dropped in the upper branches of a host tree. It sends roots down around the trunk, grows slowly, and eventually the host dies while the fig stands in its place, never once knocking the original tree down to grow.
Applied to software, the mechanics look like this. A new system gets built alongside the existing legacy one, not instead of it. A routing layer sits in front of both and decides, per request, which system handles it. New capabilities get built in the new system, and the routing layer gradually shifts traffic over, one capability at a time. Everything that hasn't migrated yet keeps running on the legacy system exactly as before. Once every capability has moved, the legacy system finally gets decommissioned.
What makes this pattern safer than a rewrite is not cleverness. It's distribution. Risk gets spread across many small, reversible steps instead of concentrated in one high-stakes cutover night. The routing layer is what makes it reversible: if a new component underperforms or breaks something, traffic points back at the legacy system immediately. Try that after a full rewrite has gone live and the old system's already switched off. There's nothing left to fall back to, which is exactly the scenario that produces headlines like Southwest's.
The pattern works best applied narrowly, one microservice or one capability at a time. Teams that try to strangle an entire application in one motion lose the very thing that made the approach safe to begin with.
Industry data on modernization programs completed in 2024 and 2025 shows organizations that prioritize their highest-impact systems first typically reach positive ROI within 12 to 14 months. McKinsey's 2026 research on enterprise modernization goes further: infrastructure cost reductions of 30% to 50%, along with 20% to 30% faster development cycles, following legacy-to-cloud migration. A separate synthesis of modernization outcomes found release cycles speeding up 40% to 60%, with security breach risk cut by roughly half.
But there's a failure mode built into this pattern that has nothing to do with the technology itself. If leadership won't commit to actually finishing the migration, a stalled strangler fig effort leaves both systems running side by side indefinitely, at double the maintenance cost of either one alone. The pattern needs a real decommission date attached to it, not just a start date and good intentions.
When incremental modernization hits its real limits
Incremental modernization doesn't always work, and pretending otherwise is its own kind of mistake. Some architectural ceilings can't get sliced away no matter how carefully the work is sequenced, and denying that just delays an expensive decision that was coming anyway.
A handful of conditions genuinely call for starting fresh:
- A single-process system that needs to become multi-region or multi-tenant. The limitation lives in the architecture itself, not in any particular file of code.
- A data model that can no longer represent the business running on top of it. No amount of refactoring fixes a schema that's the wrong shape for what the company does now.
- Source code that's simply gone, lost with a contractor who never handed it over properly. There's no test harness to write against code that doesn't exist anymore.
- A very small application, where a full rewrite takes weeks rather than years. At that scale, the risk is low enough that the economics flip in favor of starting clean.
- A scope change so large that 90% or more of the existing code no longer serves what the business needs today. At that point, incremental refactoring is mostly deletion anyway, and starting clean can end up cheaper.
The test for telling a real ceiling from a convenient excuse is the same one from earlier: ask what specifically prevents incremental modernization. A genuine constraint is nameable: it might be a specific file, a specific table, or a specific deployment model that won't flex. "It's just too old" is a freedom. It's an opinion dressed up as one.
Even where a real ceiling exists, the decision should still start with rationalization, not skip past it. Inventory the system, score it honestly, and confirm the ceiling is actually there before signing up for the cost and disruption a full rewrite always brings.
How to sequence the first modernization moves in practice
Order matters here more than almost anything else. Getting the sequence wrong is how modernization projects turn into the $370 million-a-year problem Pegasystems flagged, and the sequence below is not optional garnish. Skip a step and the ones after it get shakier.
Step one is application rationalization, before a single line of code gets touched. Inventory every system, score each on business value, maintenance cost, technical fitness, and risk. The output should be a ranked list, not one yes-or-no decision made about the whole portfolio at once.
Step two is characterization tests on whatever systems are moving first. Document current behavior thoroughly enough that changes can be verified against it later. This step also tends to surface hidden dependencies and knowledge gaps early, turning what could've been a failure halfway through a migration into a manageable surprise on day one.
Step three is API wrapping around the highest-pain integration points, the parts of the system other teams depend on most and understand least. This creates stable boundaries new components can build against, isolates the messy legacy internals behind them, and lets new development start without waiting for a full replacement to exist.
Step four is strangler fig displacement, starting with whichever capability has the clearest boundary and the biggest business impact. Prioritizing the highest-impact piece first is exactly what drives that 12-to-14-month path to positive ROI mentioned earlier. Starting with the easiest, lowest-impact piece feels safer but usually just delays the payoff, and delay here has a cost, not just a feeling attached to it.
Step five, infrastructure modernization, rehosting or replatforming, can often run in parallel with the steps above, or follow right after. Getting off aging on-premise hardware is frequently separable from application-layer work, and it tends to drop maintenance costs fast.
After that, the work doesn't really end. It becomes a standing practice instead of a project with a finish line: proactive technical debt management, continuous updates, architecture review built into the normal rhythm of engineering rather than something that only happens once a decade in a crisis. Organizations that avoid the next modernization crisis are, almost without exception, the ones treating this as ongoing maintenance rather than a one-time fire drill.
What does running this sequence actually require? Someone who can hold the whole picture at once: rationalization, test coverage, API design, incremental displacement, and who stays through the full process instead of handing over a plan document and disappearing.
For founders running lean teams on top of MVPs built fast, sometimes by freelancers, sometimes largely with AI tools, the same sequence applies without much modification. Characterize what already exists. Wrap the riskiest boundaries first. Displace capability by capability rather than all at once. With paying users already depending on the product every day, the real difference is a smaller window to get the sequence right before something breaks in front of them.


