Last updated

Software Development

Legacy system modernization without downtime, when the two systems disagree

Legacy system modernization without downtime depends less on routing traffic than on rules agreed before any traffic moves. Those rules cover the day the old and new systems disagree: which one is the system of record, what counts as a match, and who can stop the cutover.

Comparing live systems and reviewing match criteria, rollback ownership and cutover checks

The morning the numbers stop matching

Some weeks into a staged migration there can be a morning like this one. It is an illustration and not a client’s project. The new billing service has been taking live traffic for six weeks, one capability at a time, as the plan said. Then the finance lead sends a screenshot. Yesterday’s invoice total is 0.3 percent higher in the new system than in the old one.

Four questions follow. Which number went to the customer? Which one goes in the ledger? Is it rounding, or a rule the old code applied that was never written down? Who decides whether the next capability moves on Monday?

If those were not answered before the migration began, the answer to each of them is “we will have a meeting”.

The published advice on replacing a system that cannot go offline is consistent and correct. Do not attempt one switchover weekend. Put a facade in front of the old system. Move one capability at a time. That advice covers routing, and most of this article covers the months that follow, when both systems are live and have to give the same answers.

We counted how much of that second part the published advice covers. On October 9, 2026 we opened the eight pages Google ranked in the US for “legacy system modernization” and noted what each one says. All eight come from companies selling modernization software or services, and none says so in the text. One names the strangler fig pattern. Three discuss running the old and new systems in parallel, only one in any depth.

None of the eight gives a rule for what to do when the two systems return different results. None mentions the batch jobs, file drops and partner integrations that go around a routing facade.

We sell this work too, as application modernization services. We rebuild and modernize business systems for a living, so we earn from the work this article describes. Each fact below is linked to its source. The advice is our judgment, and we say so where we give it.

What counts as a legacy system

A legacy system is one the business still depends on but can no longer change safely, cheaply or quickly enough. Age is a symptom of that and does not define it. A ten-year-old system with tests, documentation and people who understand it can still be changed. A five-year-old one that only its departed author understood may already be legacy.

The clearest published examples come from the US Government Accountability Office. In a July 2025 report it listed eleven federal systems most in need of modernization. Two Treasury systems that support tax processing are 59 and 51 years old and written in COBOL and assembly language. A Defense system that manages contracts is 60. Others handle payroll, human resources, aeronautical information and the operation of dams and power plants.

Eight of the eleven used outdated languages. Seven had known security vulnerabilities that could not be fixed without modernizing.

Private examples look less dramatic and are far more common. An ERP heavily customized fifteen years ago is one. So is a desktop application on a database version its vendor stopped supporting, or a spreadsheet with macros that runs month-end.

SAP ERP 6.0 is a live case. SAP has said that mainstream maintenance for the core applications of SAP Business Suite 7, which include it, ends at the end of 2027. Optional extended maintenance runs to 2030. A system that works perfectly well today is on a clock.

What leaving it alone costs

Leaving a legacy system alone costs most in maintenance spend and delay. The two most reliable measures of both come from government auditors, who have no service to sell.

The GAO reported in July 2025 on planned US federal IT spending for that year. About 79 percent of it, around 83 billion dollars, was meant for operations and maintenance and not for new capability. It also reported that agencies had completed modernization of three of the ten most critical legacy systems it identified in 2019.

The UK National Audit Office looked at five large digital programs. Together they ran about 26 percent over their original forecasts and built up at least twenty-nine years of delay, partly from keeping legacy systems running longer than planned. In a separate review it found departments lacked fully funded plans to remediate roughly half of their legacy estate. Its conclusion was that continuing to use legacy services is risky and commits organizations to even higher future costs.

You will often be told how many companies still run legacy systems, or what share of the Fortune 500 still runs COBOL. We looked for a primary source for those counts and did not find one, so we do not quote them. The eight pages we read do not quote them either. They quote five figures between them, all about IT budgets, and two of those are about technical debt and not about legacy systems.

Other figures repeated across this field do not hold up. The competing counts of COBOL lines in production differ by a factor of three. One comes from a vendor-commissioned survey and the other from a trade report whose source we could not identify. The trillions-of-dollars technical debt figure is, by its own report, mostly an estimate of cybercrime.

The 79 percent maintenance share has a limit too. It is specific to the US federal government. No audited private-sector equivalent exists, so anyone quoting it as a general business fact is stretching it.

Choosing a route for legacy system modernization

You modernize legacy code by first choosing a route for each system, from switching it off to rebuilding it. There are six or seven routes depending on whose list you read, and most estates use several at once. Amazon’s prescriptive guidance calls them the 7 Rs.

Two of the routes move nothing. You retire a system that is no longer used, and Amazon suggests checking for applications with no inbound connections for 90 days. You retain a system, for now, when it is too risky to move, too dependent on something else, or not worth moving.

The next routes move the system without redesigning it. Rehosting, also called lift and shift, puts it on new infrastructure unchanged. That is cheap and fast, and it leaves the code as hard to change as before. Relocating or replatforming moves it with modest changes, such as a managed database or a newer runtime.

Repurchasing replaces the system with a product, and the work becomes data migration, integration and training. If a product might cover the ground, that is a different decision from the one this article follows. If the system is an ERP, the comparison discipline described here also decides what the switchover really costs.

The last route is to refactor or rebuild, which means redesigning the system so it can change again. Amazon calls this the most complex and costly route. It is the one the rest of this article is about.

Settle one thing before you start. The goal of a rebuild should be what the business needs now, which is different from what the old system does. Thoughtworks put rebuilding to feature parity on hold in its own technology radar. Its argument is that current processes are largely a consequence of the workarounds the legacy technology forced. Thoughtworks sells modernization consulting, and the argument stands anyway.

When the answer is refactor or rebuild, the code-level technique matters. Inside a codebase the technique is branch by abstraction, a name Paul Hammant introduced. Martin Fowler describes it as a way of making a large-scale change gradually. It puts an interface in front of the part you are replacing so old and new implementations can coexist. Across systems, the same idea becomes the strangler fig pattern.

What the strangler fig pattern moves, and what it leaves behind

The strangler fig pattern gives you incremental delivery and request routing. That is valuable, and it is why the approach is right. It leaves the data underneath untouched.

Martin Fowler named it in 2004 after strangler figs he had seen in Queensland. The plants seed in a host tree’s branches and work down until they root and the host is gone. His current write-up supersedes the original. Microsoft documents it as the strangler fig pattern and Amazon in its cloud design patterns.

The mechanism is simple. A facade sits in front of the legacy system and at first forwards everything to it. As each capability is rebuilt, the facade routes that capability to the new code. Callers never change. The old system loses responsibilities one at a time until it holds nothing and can be switched off.

The pattern does not address state. For as long as both implementations are live, the data has to be in two places, or in one place understood two ways. Something has to keep the two in agreement. Amazon’s own guidance is candid about it. It describes redundancy and eventual consistency between two data stores as a tactical position and not a destination.

When a staged migration is the wrong call

A staged migration is the wrong call when the system is small enough to rebuild and cut over in one short window. The parallel-run machinery then costs more than the risk it removes. There are three other cases.

The first is a system where you cannot intercept requests and cannot modify the legacy code. With no facade and no hook for replication, there is nowhere to stand.

The second is a data model so entangled that no capability can be carved out without most of the schema. Untangle it first, or accept a full rebuild. This case also answers a question buyers ask early, which is how long a rebuild takes. It depends far more on how entangled the data model is than on how much code there is.

A system whose capabilities separate cleanly moves in stages of weeks. One where every table touches every other has to be untangled before any staging is possible, and the untangling is the larger project. So ask how small the first shippable stage can be before you ask how long the whole program will take. If no capability can be carved out and cut over on its own, that finding should reshape the plan before anything else does.

The third is an organization where no one can own the comparison between the two systems. Every decision in the sections below needs a person. Without one, a staged migration runs two broken systems where there used to be one.

A business-critical system usually fits none of these cases, and staging is usually right for it. The rest of this article assumes that route.

Keeping two live systems in agreement

You keep two live systems consistent by committing each change once and replicating it from a log, then backfilling history separately. You do not write to both from the request. The details are where teams lose months.

Two flows: live changes go from the old system through a change log to the new system, and historical records go from the old data through a backfill to the new store (opens full size in a new tab)
Each change is committed once, to the old system, and copied from its log. Historical records are backfilled separately.

Dual writes fail in the middle

Stripe published the canonical sequence. Dual write to old and new, move reads once you trust them, then move writes, then remove the old data. It is a sound plan. The naive version of its first step breaks in two ways, which Martin Kleppmann set out in why dual writes are a bad idea.

The first is partial failure. The second write fails after the first has committed, no transaction spans both, and nothing knows they have diverged. The other failure is worse. Two clients write at once, and the systems receive the requests in different orders. Both end up internally consistent, permanently different and silent about it.

The fix is to stop treating the second write as part of the request. Commit once, and record in the same transaction that a change happened. A separate process then applies it to the other system, retrying until it succeeds.

Engineers call this a transactional outbox. The change and a note that it needs replicating sit in one table, written in one transaction, and a relay works through the notes in order. It costs a table and a small process. It saves you from the class of bug that only appears under load, on the busiest day of the quarter.

Change data capture gives the same ordering guarantee from the other side, by reading the database log instead of the application. It is usually the better option when you cannot touch the legacy code at all.

Backfill needs a plan of its own

Replication covers changes from the moment it is switched on. Everything older has to be copied while the system stays live. Plans routinely give that one line when it deserves several. The copy must run in chunks small enough not to starve production and be throttled against real load. It must restart from where it stopped and cope with rows changing underneath it.

Order matters. Backfill first and replicate second, and you miss whatever changed in between. Replicate first, then backfill with a rule that a replicated change always beats a copied one, and the two meet cleanly.

The old system keeps changing underneath you

A migration that runs for months runs beside a business that still needs changes. The legacy schema will move while you migrate, and you need a way to absorb that without breaking replication.

The discipline is parallel change, documented by Danilo Sato and more often called expand and contract. Add the new shape beside the old, move every reader and writer across, and only then remove the old. Each step is reversible on its own, which is what you want while reversibility is your safety net.

Fowler makes the matching point about deployment order. Change the schema to support both versions of the application first, confirm it, and only then deploy the new application.

The organizational part is a freeze people will accept. No business agrees to a freeze on all legacy change. What works is a rule about which tables are in scope for the current stage. Urgent changes then need a route that lands them in both systems at once.

What to settle before the first comparison

Before the first comparison runs, settle seven things in writing, because the old and new systems will disagree. Two implementations written years apart by different people will not match on every record. When they differ, you quarantine the record and resolve it against the system declared as the record for that capability. The comparison is the only place a disagreement between the two ever surfaces, so its rules have to exist first.

Five steps when the systems disagree: detect a difference, quarantine the record, check the system of record, resolve with a named owner, and run the comparison again (opens full size in a new tab)
Agree on this sequence, and who owns each step, before the parallel run starts.

Take the finance lead’s first two questions from the opening. While both systems run, exactly one is the system of record for each capability, and it should be named in writing. Finance, support and your auditor all need to know which. It decides whose number goes on the invoice on the first day the two differ.

Next, rank the entities by what a wrong answer costs. A rounding difference on an internal dashboard is noise, and the same difference on a tax filing is a problem. Let that ranking set how hard you compare each entity and the order you migrate them. Start where being wrong matters least, because that is where you learn.

The third question was whether the 0.3 percent is rounding or a missing rule. You can only tell if the differences that will never matter are already separated out. A naive comparison lights up on timestamp precision, floating point rounding, sort order and sequence numbers. Publish the exclusion list and the tolerance per field before you start. Otherwise the team spends its first fortnight chasing noise, and learns to ignore the alert just in time for the one that matters.

Compare outputs and leave the screens out of it, because two interfaces can look identical and compute differently. Compare what the business consumes, such as the export, the ledger entry, the invoice total and the file the regulator reads. Run both systems on the same input and diff the result.

Set the parity threshold and the observation window before you see a result. Decide what share of records must match, across which entities, sustained for how long. A threshold chosen after the results are in gets argued down until it fits the date already announced.

When a record does diverge, hold it and raise it. Never let either system silently overwrite the other. Auto-correction destroys the evidence you need to find the cause, so a migration that repairs its own symptoms keeps every bug and loses the trail.

The last question was who decides about Monday. Give one named person the authority to stop the cutover. It should not be a committee, and it should not be the delivery lead, whose incentive is the date. Until someone is empowered to trigger it, a rollback plan is a document and controls nothing.

Rollback has an expiry date

Rollback stops being possible at the first write that lands only in the new system, unless you built and tested a reverse path in advance. Most plans say there is a rollback plan. Few say what triggers it, who calls it, or when it expires, and that is why teams sit at ninety percent parity for months.

Write the trigger down as a metric, a threshold and a window, in the same document as the parity target. Then write down how long reversal stays available at each stage.

What can still be reversed at each stage of a cutover, and what closes the door.
What you are reversingStill reversible whileWhat closes the door
Read trafficThe legacy path still exists and is still fedVery little. This is the easy direction, which is why reads move first
Write traffic, before the new system holds anything uniqueEvery write still reaches the legacy system tooThe first write that lands only in the new system
Write traffic, after the new system holds unique dataYou built and tested a reverse path that replays new records into legacyNo reverse path was built, the usual case, because a migration that runs backwards was never budgeted for
A schema changeThe old column or table still exists beside the new shapeThe contract step ran and the old shape was dropped
A whole capabilityThe legacy code, tables and scheduled jobs are still deployedLegacy objects were deleted to tidy up before the window closed

The third row catches people. Reversing writes after the new system has accepted records the old one never saw needs a reverse migration, built and tested in advance. Almost no plan includes one. If yours will not, say so, and treat the first unique write as the moment the decision becomes permanent.

Fowler notes the same limit in blue green deployment. Transactions the new environment accepted while it was live still have to be dealt with.

Rehearsal separates calm cutovers from frantic ones. GitHub built gh-ost for online schema changes. It could run a migration against a replica, swap the tables and swap them back. The team reported thousands of such migrations before running one on a production primary.

The final cutover was deliberately deferred so a person chose the moment. Rehearse the reversal as well as the change.

Everything that goes around the facade

A facade only intercepts requests, so anything that reaches the legacy system another way goes straight past it. Each of those paths needs its own migration and its own parallel run.

Nightly batch and ETL jobs never make a request through your proxy. Treat each job as a migration stage with its own comparison and cutover. Month-end close and scheduled reports run on a calendar, so a defect can hide for four weeks. Run at least one full close on both systems before moving them.

File drops over SFTP or EDI have no request to intercept, only a file appearing. Produce the file from both systems and diff the output before switching the producer. Reports and tools that read the legacy database directly are in the same position.

Then there are the paths outside your control. Partners post to a legacy hostname you cannot make them change. Keep the old endpoint alive as a thin forwarder and give them a dated window to move. Anything behind an IP allowlist breaks when the new host has a different address. Raise that change weeks ahead, because a third party’s change process runs on its own timetable.

Webhooks registered years ago against old URLs live inside other companies’ systems. Inventory every callback before cutover and assume you will miss some. A webhook registered by someone who has since left, pointing at a hostname you are about to retire, cannot be found from your own codebase. These surface on cutover night.

When to switch the old system off

Switch the old system off when exit criteria agreed in advance are met. That means named entities matching at a set proportion for a set number of consecutive days or closes, signed off by the people who use the system. Write those criteria at the start, next to the parity threshold.

A parallel run costs real money and attention. There are two systems to operate and two sets of alerts. Someone on call has to understand both well enough to say which is wrong at three in the morning. That is acceptable for a defined window and corrosive without one.

Decide up front who is paged when the comparison flags a divergence overnight, and what they are allowed to do about it before morning. “Hold the record and wait” is a valid answer only if someone wrote it down.

A staged migration has one failure worth planning against from the start: a parallel run that becomes the permanent architecture, because no one defined what finishing looked like.

For anything that touches finance, the window should cover at least one complete month-end close. The sign-off should come from the people who operate the system daily as well as from engineering. They notice when a report is subtly wrong, and they carry the cost if it is.

All of this is decided before any traffic moves. You name the systems of record, agree the tolerances, build the reverse path or admit you will not, and write down who can stop the cutover.

A plan that does not yet answer those points deserves a second reading before any traffic moves. Send us the brief and the plan as it stands. We sign an NDA first. Then, on a 30 minute call, the engineers who would run the migration will go through the plan and mark where it is likely to stall.

Questions about modernizing without downtime

How can I modernize legacy code?

Choose the route first: retire, retain, rehost, replatform, repurchase or rebuild, which Amazon’s guidance calls the 7 Rs. If you rebuild, do it incrementally: branch by abstraction inside a codebase, and the strangler fig pattern across systems, so old and new run side by side and capabilities move one at a time. Decide in advance how you will compare the two and who can stop the cutover.

What is a legacy system example?

The US GAO’s 2025 report lists federal examples, including two Treasury tax processing systems 59 and 51 years old written in COBOL and assembly language. In business, a typical example is an ERP customized years ago that nobody can change safely, or an application on a database its vendor no longer supports.

Is SAP a legacy system?

SAP ERP 6.0, often called ECC, is heading that way: SAP’s mainstream maintenance for Business Suite 7 core applications ends at the end of 2027, with optional extended maintenance to 2030. SAP S/4HANA, its successor, has a maintenance commitment to 2040.

What happens when the old and new systems disagree?

Quarantine the record rather than letting either system overwrite the other, and resolve it against the system declared as the record for that capability before the migration began. Agree in advance which differences do not count, such as timestamp precision and rounding, and the parity threshold that allows the next step.

Can you roll back after go-live?

Reads, almost always. Writes, only until the new system holds records the legacy system never received, and after that only if you built and tested a reverse path in advance. Treat the first such write as the point where the decision becomes permanent.

How long should you run both systems in parallel?

Long enough to cover a full business cycle for the capability, which for anything financial means at least one complete month-end close, and long enough to hit the agreed parity threshold on consecutive runs. Set the exit criteria at the start, or the parallel run quietly becomes permanent.

The author

Mayursinh Jadeja

Founder · 13+ years in software

Mayursinh Jadeja founded Redlio Labs in 2014 and has led it since. He works from Ahmedabad, India, leading the company’s software, design and growth work for organizations across the US, UK, Europe, Australia, and the UAE, and the scoping conversations that decide what a program will cost. He writes the software, design and delivery articles on this site and traces every figure in them to a primary source.

  • Custom software delivery
  • ERP systems
  • Product design and UX
  • Next.js, React, and Node
  • Project scoping
Full profile

Keep reading

Start with a conversation, not a contract

Bring us the system to build, the product to design, the inquiries to grow, or the team to extend.

We use what you send to understand and answer your inquiry. Privacy policy

  • We reply within 12 hours
  • You talk to the people doing the work, never an account manager
  • Peer-reviewed releases
  • NDA before your first call
Mayursinh Jadeja

Mayursinh Jadeja

Founder, Redlio Labs

Rated 5.0 on Clutch and 5.0 on Google