Milo Solutions
Get in touch
Legacy System Modernization: How to Upgrade Without Breaking Everything

Legacy System Modernization: How to Upgrade Without Breaking Everything

• 14 min read

Introduction

Most of the well-known legacy systems examples did not fail because they were old, but at the moment somebody touched them, and that distinction is the whole point of this guide because it changes what you actually plan for.

I run software projects at Milo Solutions, and much of what we do is taking over an older system a business still depends on and changing it without the business noticing. The systems that make the news are the ones where a migration or an upgrade went sideways, not the ones that quietly kept running for another twenty years. So before we talk about how to modernize, it helps to be honest about what makes a system legacy in the first place.

For us, a legacy system is usually not about one specific technology. It is more often an older application that has been running for many years and has been changed by different people and teams over time.

Key takeaways

  • Age is not the test for whether a system is legacy; the tests are whether it is unsupported, unmodifiable, or unknowable, and a stable, understood system can be the right thing to keep.
  • The dangerous moment is the cutover rather than the years of running, and most public failures passed functional testing before falling over on operational load or during data reconciliation.
  • The work that most reduces risk happens before you change anything: characterization testing, mapping the hidden integration surface, and reconstructing the business logic nobody wrote down.
  • Incremental cutover beats a big-bang replacement in terms of blast radius, so keep the old system reversible until the new one has handled a real month-end.

What actually makes a system legacy

The useful definition is not about a date; a system becomes legacy when one of three things is true: it is unsupported, so nobody ships security fixes for it anymore; it is unmodifiable, so changing it safely is beyond the people you have; or it is unknowable, so no one can say with confidence what it does. Plenty of old software is none of those things, and plenty of software only a few years old is already all three.

The unsupported part you can check this afternoon, because Microsoft's own lifecycle calendar records that Windows 10 in every edition, Office 2016 and 2019, Exchange Server 2016 and 2019, and Visual Studio 2015 all reached end of support on October 14, 2025. If your estate still runs any of those in production, part of it crossed the line into legacy on that date, whatever it looked like the week before.

The unknowable part is the one that costs the most, and it is where I spend most of my attention.

The biggest problem is usually that nobody fully understands how the system works anymore. Documentation may be missing, some rules are hidden in the system, and even small changes can sometimes be risky.

The real trigger for action is not that the system is old but that it has drifted out of anyone's understanding. Deloitte's 2026 Global Technology Leadership Study puts the drag in financial terms, estimating that technical debt accounts for 21% to 40% of an organization's IT spending, with around 60% of leaders believing a further 21% to 50% of value is trapped in existing technology. Those numbers are the accumulated cost of systems that got harder to change, one undocumented decision at a time.

Legacy systems examples that prove the point

Public records provide better examples of legacy systems than any vendor case study because they are documented by auditors rather than marketers. The most current inventory comes from the US Government Accountability Office. Its July 2025 report examined 11 critical federal systems ranging from 23 to 60 years old, found that eight use outdated languages, four run on unsupported hardware or software, and seven carry known cybersecurity vulnerabilities, and concluded that only three of the eleven had a complete modernization plan. An earlier GAO review had already put the cost of running ten of these systems at about $337 million a year.

These systems are old, and they mostly work, and the trouble starts only when they get moved, which is where aviation is the clearest example. The Department of Transportation's testimony to the Senate on the January 2023 national ground stop describes the FAA's NOTAM system as running on 30-year-old software and architecture, and attributes the failure not to age but to contract personnel unintentionally deleting files while syncing the live primary database to its backup. The system had run for three decades and then fell over during routine maintenance.

Banking provides the best-documented case of all: when TSB migrated its data to a new platform in April 2018, the transfer succeeded, but the platform failed immediately. The Financial Conduct Authority's enforcement notice records that a significant proportion of 5.2 million customers were affected, that business as usual was not restored until December 2018, that TSB paid £32.7 million in redress, and that the bank was fined £48.65 million in total. The transfer moved the data cleanly, and the platform broke anyway, which is exactly why a clean data migration is not the same thing as a successful cutover.

Public-sector ERP projects show the same pattern, but on a slower timeline. Birmingham City Council budgeted around £19 million for an Oracle implementation and, as Computer Weekly reported, ended up spending well over £100 million, with the root cause traced to customizing the new system to fit old processes and to users being unprepared for the change. Lidl reportedly abandoned a roughly €500 million SAP project in 2018 after about three years, according to Computer Weekly's account of the German trade press. None of these failures were caused by the age of what came before; they were caused by moving off it.

These are cross-industry patterns rather than one-off stories, and our guide to digital transformation examples collects more of them. A real Milo modernization example would sit well alongside these public cases, and one can be added once a client agrees to be named.

Why do systems break during modernization, not before it

Put the public cases together, and the thesis is hard to argue with. A legacy system is a known quantity right up until you change it. The change is what converts a stable liability into an active incident, because the change is the first thing in years that tests what the system actually does rather than what everyone assumes it does.

That matches what I see in practice, and I want to be honest about the frequency, because most articles on this topic imply disaster is the default, when in practice it usually is not.

We don't normally have projects where a migration or upgrade goes seriously wrong. It has happened, but very rarely.

In one case, we discovered during the upgrade that some parts of the existing system relied on undocumented legacy behavior. What looked like a simple change turned out to affect other areas of the application.

We caught it early, adjusted the approach, and avoided a bigger issue. For me, this is the main risk with modernization: you don't always know what the existing system is doing until you start changing it. Good discovery, testing, and doing changes step by step help us keep that risk low.

Serious failures stay rare for us, not by luck but because the risk is predictable and specific: undocumented behavior surfaces the moment you change the code around it. Once you know that is the failure mode, the work reorganizes itself around finding that behavior before it finds you.

The work you do before you touch anything

This is the section the typical modernization article skips, and it is the one that decides the outcome. The goal of the first phase is not to write new code but to make the system knowable again.

When we take over a system with little or no documentation, we first focus on understanding how it works before making any changes.

We look at the code, database, integrations, infrastructure, logs, and the main user flows. We use tools like GitHub, Jira, and monitoring and database tools to help us understand the system.

Reading the code and the database tells you what the system is made of. It does not tell you what it does under load, which is where behavior hides. The technique that closes that gap is characterization testing: you wrap the existing system in tests that capture its current outputs exactly as they are, bugs included, so you have a golden master to compare the new system against. You are not judging whether the old system is correct, only recording what it does, so that you can prove the replacement behaves the same way.

Alongside that, map the integration surface, because it is almost always larger than the client thinks. Nightly batch jobs, file drops to a partner, an EDI feed, a finance report that someone downloads every month, a spreadsheet that quietly became load-bearing. These are the connections that turn a technical migration into a business incident when they are discovered after cutover rather than before.

Our experience as a company is also a key part of this process. After working on many different systems, we know where to look for potential problems and hidden dependencies.

Sometimes we find business rules in the code that even the client didn't know about. That's why we always spend time understanding the existing system first, before making changes.

The rules buried in a twenty-year-old system are not documented anywhere except in the code, and they are often the reason the business works the way it does. Rebuild without recovering them, and you ship a system that is technically correct and operationally wrong.

Incremental cutover versus replacement

Once the system is known, the biggest single decision is how you move off it. A big-bang replacement, where the old system switches off and the new one switches on over a weekend, concentrates all the risk into one irreversible moment. Every failure in the examples above was effectively a big-bang cutover. The alternative is to move in small pieces, and the pattern that formalizes this is the strangler fig.

The idea is straightforward: you put a facade in front of the old system, then move one capability at a time behind it to the new system, routing each request to whichever side now owns it, until nothing is left running on the old system and you can retire it. Microsoft's Azure Architecture Center and AWS Prescriptive Guidance both document the pattern in detail, including an anti-corruption layer to stop the old system's data model from leaking into the new one.

Both also list, honestly, when not to use it: if requests to the system cannot be intercepted cleanly, or the system is small enough to replace in one safe step, the facade adds cost without reducing risk, which is why incremental cutover is the right default rather than a universal rule.

Data migration and reconciliation

The most dangerous belief in any migration is that moving the data is the hard part, when moving the data is the easy part, and TSB moved its data successfully, yet still failed. The hard part is reconciliation: proving that the data in the new system means the same thing, produces the same balances and reports, and behaves the same way as the data in the old one.

The tool for this is a parallel run, where you keep the old system live, run the new system alongside it using the same real inputs, and continuously compare their outputs. Discrepancies show up as differences between two systems processing the same day, not as customer complaints a week later.

A parallel run costs real money because you are running and paying for two systems and two operations teams at once, but it is the difference between finding a reconciliation error in a dashboard and finding it in a regulator's letter.

Making the cutover reversible

Assume the cutover will surface something you did not predict, and design so that it does not matter. Reversibility means three things in practice: a rollback plan you have actually tested, feature flags that let you shift traffic back to the old system per capability rather than all at once, and explicit go/no-go criteria agreed before the day so the decision to proceed is based on evidence rather than momentum.

The bar for "it works" is not that the new system passed a functional test, because both TSB and Birmingham passed functional acceptance; the bar is non-functional parity, meaning the new system handles Monday morning peak and the month-end close at the same throughput as the old one. A system that is correct on a quiet afternoon and falls over on the last working day of the month has not been modernized so much as broken on a schedule.

When not to modernize

The honest position, and the one most articles avoid, is that modernizing everything is often the wrong answer. Sometimes the right move is to change less, or to wait.

Yes, we have had situations where we advised a client not to modernize everything at once.

Sometimes the best solution is to split the project into smaller batches. As a company, we might get less benefit at the beginning, but it can reduce risk, control costs, and help us learn from each stage before moving forward.

That advice costs us work in the short term, and we give it anyway.

We are honest about this, even if it means there is less work for us in the short term. We believe that being transparent is more important than selling a bigger project. If we give the client the right advice and deliver good work, we trust that they will come back to us when they are ready for the next batch.

For us, the goal is not to modernize everything just because we can. It's to recommend what actually makes sense for the client's business.

Part of the reason to be cautious is the cost nobody budgets for: the overlap period. For as long as the migration runs, you pay for both systems, both operations teams, and the sync layer between them. If you bring in an outside team to run the migration, a dedicated team tends to fit uncertain work better than a fixed-scope contract.

If you rush the batches to shorten that overlap, you reintroduce the big-bang risk you were trying to avoid. So the decision is a real trade-off, not a foregone conclusion. If a system is supported, understood, and contained, keeping it can be the correct engineering decision, and the trigger for action should be that it is unsupported, unmodifiable, or unknowable, not the number of years on the clock.

Frequently asked questions

What is a legacy system?

A legacy system is one that has become hard to support, change, or understand, regardless of its age. The practical test is whether it is unsupported, unmodifiable, or unknowable. A system that is old but stable, documented, and still maintained is not really legacy in the way that matters.

What are examples of legacy systems?

Documented public examples include the 11 critical federal systems aged 23 to 60 years in the GAO's July 2025 inventory, the FAA's NOTAM system running on 30-year-old architecture, and the older banking and ERP platforms behind the TSB and Birmingham City Council failures. Common private-sector examples are aging line-of-business applications built in older languages and estates running software that has passed its end-of-support date.

How do you modernize a legacy system without downtime?

Move incrementally rather than all at once, using a pattern such as the strangler fig, run the old and new systems in parallel to reconcile data and behavior, and keep the cutover reversible with feature flags and a tested rollback plan. Downtime risk comes from the cutover, so the goal is to make each step small and recoverable.

Should every legacy system be modernized?

No, and that is worth saying plainly, because a supported, understood, and contained system can be the right thing to keep. Modernization is worth the risk when the system is unsupported, can no longer be changed safely, or nobody understands what it does. It is often smarter to modernize in batches than to replace everything at once.

Sources

  • US Government Accountability Office, "Agencies Need to Plan for Modernizing Critical Decades-Old Legacy Systems" (GAO-25-107795), July 17, 2025. https://www.gao.gov/products/gao-25-107795. Cited: the 11 federal systems aged 23 to 60 years, their vulnerabilities, and the finding that only three had complete modernization plans.
  • US Government Accountability Office (GAO-23-106821), May 10, 2023. https://www.gao.gov/products/gao-23-106821. Cited: the roughly $337 million a year to operate ten critical legacy systems.
  • US Department of Transportation, testimony on the FAA NOTAM system failure, February 15, 2023. https://www.transportation.gov/federal-aviation-administrations-notam-system-failure-and-its-impacts-resilient-national-airspace. Cited: the 30-year-old software and the file-deletion cause of the January 2023 ground stop.
  • Financial Conduct Authority, "TSB fined £48.65m for operational resilience failings," December 20, 2022. https://www.fca.org.uk/news/press-releases/tsb-fined-48m-operational-resilience-failings. Cited: the migration failure, 5.2 million customers, restoration in December 2018, and £32.7m redress.
  • Computer Weekly, "Birmingham City Council's Oracle implementation explained." https://www.computerweekly.com/news/366572935/Birmingham-City-Councils-Oracle-implementation-explained-What-went-wrong. Cited: the £19m budget against £100m+ actual, and the customization root cause.
  • Computer Weekly, "Lidl dumps €500m SAP project," August 16, 2018. https://www.computerweekly.com/news/252446965/Lidl-dumps-500m-SAP-project. Cited: the abandoned project (secondary source reporting the German trade press).
  • Deloitte Insights, "2026 Global Technology Leadership Study" technical debt analysis, March 27, 2026. https://www.deloitte.com/us/en/insights/topics/technology-management/technical-debt-impact.html. Cited: technical debt at 21% to 40% of IT spending and the trapped-value finding.
  • Microsoft Learn, "Products reaching end of support on October 14, 2025." https://learn.microsoft.com/en-us/lifecycle/announcements/october-14-2025-products-end-of-support. Cited: the end-of-support date for Windows 10, Office 2016/2019, Exchange 2016/2019 and Visual Studio 2015.
  • Microsoft Azure Architecture Center, "Strangler Fig pattern." https://learn.microsoft.com/en-us/azure/architecture/patterns/strangler-fig. Cited: the pattern, the anti-corruption layer, and when not to use it.
  • AWS Prescriptive Guidance, "Strangler fig pattern." https://docs.aws.amazon.com/prescriptive-guidance/latest/cloud-design-patterns/strangler-fig.html. Cited: the implementation steps and named anti-patterns.

Disclaimer

This article is general guidance for planning a modernization, not specific technical, legal, or compliance advice for any particular system. In regulated sectors a cutover has compliance and audit implications that depend on your obligations, so treat the steps here as a starting point and involve the right specialists before you commit to a plan.

Portrait of Kacper Gazda

Kacper Gazda

Milo Solutions | CEO

Kacper Gazda is the founder and CEO of Milo Solutions, a custom software development company he has run since 2010. An engineer by training, he holds an MSc in Software Engineering from Lublin University of Technology and has spent over 16 years building web and mobile products for startup founders and established businesses. He also works as a fractional CTO and invests in early-stage technology companies. Kacper writes from direct experience running dedicated development teams, rescuing inherited codebases, and helping clients decide what to build and how to build it.