"Let's just rewrite it" is the most expensive sentence in enterprise software. We've seen big-bang rewrites of legacy systems blow budgets, miss deadlines by years, and — in the worst cases — get scrapped entirely after the business realized the old system, warts and all, was still running the company the whole time. Here's the roadmap we use instead.
Step 1: Map before you touch anything
Before writing a line of code, we build a complete map of what the legacy system actually does — not what the documentation says it does. That means tracing real data flows, interviewing the people who use it daily, and cataloging every downstream system that depends on it, including the "temporary" integrations built five years ago that nobody remembers approving.
Step 2: Find the seams
Every legacy system has natural boundaries — modules, data domains, or workflows that are more loosely coupled to the rest than they appear. The strangler fig pattern works by identifying these seams and routing traffic for one slice at a time to a new service, while the legacy system keeps handling everything else untouched.
- Start with the highest-pain, lowest-risk module — usually something with a clear input/output contract and few hidden dependencies.
- Build the new service alongside the old one, not as a replacement yet.
- Put a routing layer (an API gateway or reverse proxy) in front of both, capable of sending traffic to either side.
Step 3: Run in parallel, verify relentlessly
Once a new module is live, we run it in shadow mode — real traffic hits both the old and new systems, but only the legacy response goes to the user. Comparing outputs catches edge cases the interviews missed, often for weeks, before the new module ever takes over for real.
The scariest legacy migrations aren't the ones where nothing has been tested. They're the ones where everything was tested against a database that doesn't look like production.
Step 4: Cut over incrementally, with a rollback plan
When a module has proven itself in shadow mode, we cut over traffic gradually — 5%, then 25%, then 100% — with monitoring at every step and a one-command rollback to the legacy path. Nobody should be able to tell modernization is happening except through smaller error rates and faster response times.
Step 5: Decommission, don't just deprecate
The step teams skip most often: actually turning the old code off. A "deprecated" system that's still running costs the same to maintain as one that's actively used, and it's a lingering source of confusion for new engineers. Once traffic has been at 100% on the new path for a defined stability window, we schedule a hard decommission date and stick to it.
Why this approach works
The business never has to bet the company on a single go-live date. Risk is distributed across many small, reversible cutovers instead of concentrated in one irreversible one. And because the legacy system keeps running until its replacement has proven itself under real production load, "the migration failed" stops being a catastrophic scenario and becomes, at worst, a rollback and a retry.