Most legacy systems are not failing. That is precisely what makes them hard.
The system still processes the orders, still closes the month, still does the thing the business depends on. What it cannot do is change. A modification that should take three days takes three weeks, nobody wants to touch the billing module, and the framework it runs on stopped receiving security patches some time ago.
That is the position we are usually called into.
When modernization is the right answer
Not every ageing system needs replacing. The signals that it does:
- Change has become disproportionately expensive. The cost is not in writing the feature, it is in being confident you have not broken something else.
- The platform is a hard stop. An unsupported runtime, a database version out of support, or hardware you can no longer buy sets a date whether you like it or not.
- Knowledge has concentrated. One or two people understand the parts that matter, and the business quietly plans around their availability.
- Integration is blocked. You cannot connect the system to anything modern, so every new capability gets built beside it instead of into it.
- Compliance is drifting. Audit trails, data retention or access controls that were adequate a decade ago no longer are.
Why we do not start with a rewrite
A full rewrite fails for structural reasons, not because a team was weak. The business does not stop for eighteen months, so every change made to the old system during the rebuild is one the new system must also absorb. Value arrives only at the very end, so there is no point at which the work can stop and still have been worth doing. And most of the requirements are undocumented, living in code written a decade ago, discovered only when the replacement gets them wrong in production.
We start instead by putting a routing layer in front of the existing system and moving capability across one slice at a time. Each migrated capability ships on its own, can be rolled back by changing a routing rule, and the half-migrated state is a normal way to operate rather than an unfinished project.
How an engagement runs
Assessment first. Two to four weeks: what the system does, what actually depends on it, where the real risk sits, and which constraint binds first. The output is a sequenced plan with costs, not a document that gets filed.
Instrument before deciding. Legacy systems typically contain a lot of code serving almost no traffic. Knowing which endpoints matter tells you what to migrate and, more usefully, what to simply delete.
Migrate vertically. One capability at a time, taking its interface, its logic and its data together, so each move delivers something complete rather than leaving a new front end waiting on an old back end.
Decide what is load-bearing. Legacy behaviour splits into two kinds: rules that have downstream consumers, and quirks nobody chose. The first must be preserved exactly, even where it is ugly. The second is worth fixing, and migration is the cheapest opportunity you will get. Telling them apart requires talking to the people who use the system.
The data is the hard part
Routing traffic is straightforward. Splitting data is where these projects actually get stuck, because for a period both systems need a consistent view of the same information.
Sharing the database while application logic moves is architecturally impure and usually the right way to start, because it removes synchronisation from the problem entirely. Change data capture comes next, where the new system keeps its own schema fed by the old system’s transaction log. Dual writes look simplest and are not: you have created a distributed transaction with no coordinator, and the two stores will diverge the first time one write succeeds and the other fails.
Whichever route, the reconciliation job gets written early. Something that compares both sides and reports differences will find problems weeks before your users do.
What you end up with
A system that can absorb the next change without another programme like this one. That is the actual goal, and it is a different goal from “modern”.