The full rewrite is the most expensive decision in enterprise software and the most frequently chosen. It’s attractive because the existing system is genuinely painful, and because a clean start is easier to imagine than a sequence of careful changes.
It usually goes the same way: eighteen months in, the new system does perhaps 70% of what the old one does, the old one has continued to change, and the business is funding two systems while getting the benefit of one.
The alternative isn’t tolerating the legacy system. It’s replacing it in pieces, while it keeps running.
Why rewrites fail specifically
The old system knows things nobody does. Two decades of edge cases, regulatory quirks and customer-specific behaviour are encoded in code nobody documented. Much of it looks like a bug until you remove it and something breaks in the finance department.
The business doesn’t pause. Regulation changes, competitors ship, customers request things. The old system keeps changing during the rewrite, so you’re chasing a moving target.
Value arrives only at the end. Nothing is delivered until switchover, so nothing is validated. If the design is wrong, you find out at the worst moment. Meanwhile, every quarter without visible benefit erodes the political support the project needs to finish.
Switchover is all-or-nothing. Cutover weekends are among the highest-risk events a business can schedule, and rollback is frequently theoretical.
Incremental strategies that work
The facade (strangler fig)
Put a routing layer in front of the old system. New functionality is built behind it as new services; existing functionality is migrated piece by piece. The facade decides what goes where. Over time the old system handles less until it handles nothing.
This is the default approach for good reason: value is delivered continuously, each piece is independently testable, and you can stop at any point with a working system. It’s rarely elegant mid-flight — you’re running both — but it’s survivable, which the alternative often isn’t.
Requires: an interface you can put a facade in front of. Web applications, APIs and service-oriented systems suit it. Tightly-coupled desktop applications with direct database access are harder, and often need step two first.
Extract the data layer first
Where multiple applications write directly to a shared database, nothing can be replaced independently. Introducing a service layer over the data — even a thin one — decouples the system so pieces can be moved.
Unglamorous, invisible to users, and usually the prerequisite for everything else.
Move the read side first
Reporting and read-only views are the lowest-risk things to extract: no writes, no transactional integrity concerns, and if something’s wrong it’s visible rather than corrupting. It’s a good way to prove the new architecture and give the business an early, tangible win.
Rehost, then modernise
Sometimes the immediate problem is infrastructure — hardware going out of support, a data centre closing, an OS reaching end of life. Moving the system as-is buys time without solving the software problem.
This is a legitimate tactic when the deadline is external, and a trap when it becomes the whole plan. Rehosting a system nobody understands leaves you with the same system, in a different place, with a modernisation budget already spent.
Replace by business capability
Rather than by technical layer, carve off whole capabilities — invoicing, then scheduling, then reporting. Each has a clear boundary, an owner, and a measurable outcome. It maps better to how the business thinks, which matters for keeping funding.
Sequencing the work
- Establish what it does. Before changing anything, characterise current behaviour: the critical paths, the integrations, the batch jobs nobody mentions. Where tests don’t exist, add characterisation tests that capture behaviour as it is — including behaviour that looks wrong. You are documenting reality, not correctness.
- Fix the ability to deploy. If releasing is a manual, risky, quarterly event, modernise that first. Everything afterwards depends on being able to ship small changes safely.
- Decouple the data. Introduce a service boundary where applications share a database.
- Extract the read side. Prove the architecture with low-risk value.
- Take capabilities one at a time, running old and new in parallel with comparison where the risk justifies it.
- Decommission deliberately, with retention of historical data planned rather than improvised.
What to do about the parts nobody understands
Every legacy system has a module people avoid. Two approaches:
Characterise and keep. If it works and changes rarely, wrap it behind an interface and leave it. Not everything must be modernised. A stable, boring component is not a problem.
Rebuild from behaviour, not from code. Where it must be replaced, derive requirements from observed behaviour — inputs and outputs over a real period — rather than trying to read the code. Run both in parallel and compare outputs until you understand the differences, then switch. Slower than a rewrite; dramatically less likely to lose something that mattered.
When a rewrite is actually right
It happens. A full rewrite can be justified when the system is small enough to rebuild in months rather than years; the technology is genuinely unsupportable, with no path forward; the business domain has changed so fundamentally that the old model is wrong rather than merely old; or you can run both systems in parallel for an extended period with real users.
If none of those apply, incremental is almost certainly the better bet — even though it’s slower, less satisfying, and harder to put in a board slide.
Frequently asked questions
How long does incremental modernisation take? Longer in total than a rewrite is projected to take, and shorter than a rewrite actually takes. The difference is that value arrives throughout rather than at the end.
Can we modernise while still adding features? Yes, and you’ll have to — the business won’t stop. Build new features behind the facade so they land in the new architecture rather than deepening the old one.
What if the original developers are gone? Common, and survivable. Behaviour is recoverable from observation, logs and tests even when intent isn’t. Budget more discovery time and resist the urge to assume the code is wrong.
Should we move to the cloud at the same time? Prefer not to. Changing architecture and infrastructure simultaneously doubles the variables when something breaks. Sequence them.
Internal links: #18 technical debt, #14 custom vs off-the-shelf, #5 when staff augmentation is wrong, /services. External references: Martin Fowler’s writing on the strangler fig pattern; Michael Feathers, Working Effectively with Legacy Code, for characterisation testing. CTA: “If a rewrite has been proposed, it’s worth a second opinion before it’s funded.” → /contact