The first instinct when a legacy system becomes a problem is to replace it. That instinct is expensive, and it's usually wrong.

A full rewrite is the highest-risk approach to modernisation. You're building a new system while operating the old one, migrating data, retraining users, and trying to replicate a decade of accumulated business logic that was never properly documented. The projects that run longest over schedule and budget are almost always rewrites. The ones that get quietly cancelled and leave the business in a worse position than before are almost always rewrites too.

That's not an argument for leaving legacy systems alone. It's an argument for being deliberate about what modernisation actually means before committing to the most expensive version of it.

Why Legacy Systems Become Problems

A legacy system is usually a system that's working — that's what makes it a legacy system rather than a failed one. The problem isn't that it stopped doing its job. The problem is that the job has changed and the system hasn't, or that the cost of keeping it working has grown faster than the value it delivers.

The specific pain points tend to cluster in a few categories. The system can't integrate with modern tooling, which means manual processes fill the gaps. It runs on infrastructure that's expensive to maintain or that carries security risk. It's built on technology that nobody on the current team understands well, so changes are slow and risky. Or it's simply reached the point where the technical debt is costing more per sprint than it would to address it.

Each of these is a different problem that calls for a different response. Treating them all as "we need to rewrite the system" is like treating a flat tyre, an overheating engine, and a broken headlight as all requiring a new car.

The Strangler Fig Pattern

The most practical modernisation approach for most legacy systems isn't replacement — it's progressive encapsulation. You build new functionality around the existing system, gradually replacing pieces of it while keeping it operational throughout. The legacy system gets smaller over time as new components take over parts of its function. Eventually — months or years later — you switch off what remains.

This is sometimes called the strangler fig pattern, after the tree that grows around its host and eventually replaces it. The metaphor is morbid but accurate.

The practical value is that it eliminates the big-bang risk. There's no moment where you switch from the old system to the new one and hope everything works. Each new component can be tested in production before the legacy piece it replaces gets retired. The business keeps operating throughout. Mistakes are isolated and recoverable.

It requires discipline. The temptation is to scope the new components so broadly that they become a rewrite by another name. The approach only works if each piece is genuinely incremental — if you can deploy and use it before the next piece is built.

The API Wrapper Approach

Before replacing anything, consider whether wrapping the legacy system in a clean API solves the immediate problem.

A lot of legacy pain comes not from the core system but from the way other systems interact with it. Direct database access, proprietary protocols, undocumented interfaces — these make the legacy system brittle and hard to change around. Putting a well-designed API layer in front of it doesn't fix the legacy system, but it does create a stable boundary. New systems talk to the API, not directly to the legacy system. The legacy system can then be changed, replaced, or extended without affecting everything that depends on it.

This is particularly useful when the legacy system itself is stable and the pain is in the integration layer. You spend weeks building the API wrapper and get years of reduced friction in return. The alternative — waiting until you've rebuilt the whole system before fixing the integration problem — means living with the pain longer than necessary.

What Actually Warrants a Full Rebuild

There are situations where a full rebuild is the right answer. It's worth being specific about what those are, so the decision is made deliberately rather than by default.

The first is when the architecture's foundations are structurally wrong — when the data model was built on incorrect assumptions about the domain and those assumptions are now embedded in hundreds of places throughout the system. You can't refactor your way out of a wrong data model incrementally. Everything built on top of it carries the same incorrect assumption. In that case, the strangler fig approach still applies, but the scope of what needs to be replaced is larger.

The second is when the security or compliance risk of the current system is active and unacceptable. Not theoretical — active. A system running on an unpatched OS with known exploits, or one that can't be made to meet regulatory requirements without changes that amount to a rebuild, has a different risk profile than one that's just slow and hard to change.

The third is when the maintenance cost has exceeded the replacement cost. This is rare, but it happens — usually in systems where the original vendor is gone, the technology stack is so specialised that support is expensive, and the team spends more time keeping it alive than using it.

The honest question before any modernisation project is: what specific problem are we solving? "The system is old" is not a problem. It's a description.

The Migration Problem

Whatever approach you take, data migration is usually the hardest part. Legacy systems accumulate data in formats that reflect historical decisions — data models that were extended informally, fields that were repurposed, records that encode state in ways that only make sense in the context of the original system's logic.

Migration strategies that work treat the migration as its own project, with its own timeline and validation criteria. You build the migration scripts early, run them against a copy of production data, and verify the output before the cutover. You run them multiple times, refining them each time until the output is consistently correct. The worst migrations are the ones treated as an afterthought — built in the final sprint, run once, and discovered to have edge cases that affect real customers.

The other underestimated part of migration is the business logic embedded in the old system that nobody documented. The rules that were added in 2011 to handle a specific customer's edge case. The calculation that uses a formula nobody can fully explain but that produces numbers the business recognises as correct. That logic lives in the old system, and it needs to be found and carried over — not assumed, not approximated. This is why legacy modernisation almost always takes longer than estimated, and why estimates that don't account for this discovery process are usually wrong.

Where to Start

The most useful thing you can do before committing to any modernisation approach is map the pain accurately. Not "the system is legacy" but specifically: which parts cause the most operational friction, carry the most risk, or block the most valuable new development? That map tells you where to start and what approach is appropriate for each area.

Often the map reveals that 20% of the system is responsible for 80% of the pain. That 20% might be replaceable without touching the rest. Or it might be so entangled with everything else that any change requires understanding the whole system first. You can't know which until you've looked carefully — and that careful look, done before committing to an approach, is the work that saves projects from going sideways.

Armin Marxer writes at zeroclue.dev.

If you're looking at a legacy system and trying to decide what to do with it — whether to extend it, wrap it, migrate away from it, or something else — we're happy to look at it with you before you commit to an approach.

Start a conversation →