Most legacy modernization projects fail one of three ways: the team tries to rewrite everything at once and ships nothing, the team migrates incrementally but never finishes, or the new system ships but the team cannot operate it. This playbook is what we use to avoid all three.
The frame: do not rewrite, replace incrementally
The single biggest decision in any legacy modernization is whether to rewrite or to incrementally replace. Rewriting feels cleaner; it almost never works. Incremental replacement (the "strangler fig" pattern, named for the way strangler fig vines grow around a host tree until they replace it) is slower per quarter but ships predictably and lets you stop at any point with a working system.
The pattern, in three sentences:
1. Wrap the legacy system in a façade — usually an API or message bus — that all new client code talks to.
2. Build new functionality behind that façade in a modern stack, routing calls to either the legacy or new implementation per feature.
3. Migrate one feature at a time off legacy, deleting the legacy code only after the new path has been in production at full traffic for at least 30 days.
Done well, the business never has a downtime window. Done badly — usually because step 3 is skipped and both systems run forever — you end up with twice the maintenance burden and no exit. Discipline on step 3 is non-negotiable.
The seven-step sequence we use
Step 1 — Map the system before you touch anything (1-2 weeks)
Before any code changes, produce three artifacts:
- Surface inventory. Every API, every UI page, every batch job, every external dependency. Most legacy systems have undocumented endpoints — find them now, not in production.
- Data flow diagram. What table is the source of truth for what entity? Where does data branch into stale copies? This is the single document that prevents the most painful migration disasters.
- Operating runbook. Who restarts the system on failure? What manual steps run weekly? What is the password recovery flow? Most legacy systems have tribal knowledge that walks out the door if a key person leaves — capture it before you start.
These artifacts pay for themselves three times over during the build. Skip this step and you will spend the project rediscovering the system you should have mapped.
Step 2 — Build the façade (2-4 weeks)
The façade is a thin API or message-bus layer that becomes the only path to the legacy system from new code. It does not change behavior; it just intermediates.
A good façade:
- Speaks the modern protocol (REST, GraphQL, gRPC) on the outside
- Translates to the legacy protocol (SOAP, custom binary, screen-scraping, whatever) on the inside
- Logs every request with structured observability so you can see traffic patterns
- Has feature flags for routing decisions per endpoint
The team's instinct is to skip this and "just call the legacy system directly from the new code". Resist. The façade is the architectural contract that makes incremental migration work.
Step 3 — Pick the first replaceable feature (1 week)
Not the easiest. Not the hardest. The right first feature to migrate is one that is:
- Self-contained (does not touch a hundred other features)
- Visible enough to validate the new stack with real users
- Reversible (easy to flip back to legacy if the new path misbehaves)
- Modest in volume (so you can monitor it carefully)
Internal tools, admin pages, and read-only reports are usually good starting points. Customer-critical write paths (checkout, payment processing, core business workflows) are usually not where you start, even though they are tempting "high impact" wins.
Step 4 — Build the replacement, dual-write, dual-read (2-8 weeks per feature)
For every replaced feature, the rollout has four phases:
1. Build new path, route 0% of traffic. Smoke test internally.
2. Dual-write. Every write goes to both legacy and new system. Compare the resulting state — they should match.
3. Dual-read with shadow comparison. Every read pulls from legacy (the source of truth) but also pulls from new. Compare results, log mismatches, do not surface to users.
4. Cutover. Once mismatches are zero across N days of production traffic, flip the read path to new. Keep dual-write for at least 30 more days. Then delete the legacy implementation.
This sequence is slow. It is also the only sequence that has a clean rollback at every step. Most modernization projects that go badly are projects where someone collapsed steps 2 and 3 into "we will just compare in staging" — and then production turned up edge cases nobody saw.
Step 5 — Migrate the data, not the schema (1-4 weeks per data domain)
Legacy schemas are usually wrong by modern standards. Resist the urge to "fix" them as part of the migration. Migrate the data into the modern stack with the legacy schema preserved (or close to it), then refactor the schema in a second project.
Reasons:
- Schema changes during a migration multiply the surface area of what can go wrong
- Most "obvious" schema improvements turn out to encode business logic the team did not realize
- A working migration with an ugly schema is a deliverable; a perfect schema with a broken migration is not
ETL the data with idempotent jobs. Run them many times in dry-run mode. Validate row counts, hash sums, and per-entity sanity checks before flipping any traffic.
Step 6 — Train the team continuously, not at the end (ongoing)
The single biggest reason post-launch modernizations fail is that the team cannot operate the new system. Avoid this with continuous training during the build, not a one-week training at the end.
Practical cadence:
- Weekly demos of the new system to the operating team while it is still being built
- Pair sessions where a team member sits with the new-system engineer for a half day per week
- Recorded walkthroughs of every operational flow as it stabilizes — uploaded to a shared library the team controls
- Runbook ownership transferred to the operating team in the last 30 days of the engagement, with the build team as backup
Training is not a project line item — it is the project. A modernization where the team learns alongside the build is a modernization that survives. One where training is "phase 9 of 9" is one where the team rejects the new system within six months.
Step 7 — Delete the legacy code (the most-skipped step)
After the new path has been in production at full traffic for at least 30 days with no rollbacks, delete the legacy implementation. Yes, all of it. Including the comments. Including the dead config.
This is the step everyone skips, and the reason "modernization" projects accumulate: at any given time most legacy modernizations have two systems running simultaneously, one of which nobody uses but everyone is afraid to delete. Schedule the delete as a hard milestone in the SOW. Defend the schedule.
Three failure modes and how to avoid them
Failure mode 1 — "Big bang" rewrites
A team decides to rewrite the entire system from scratch, run both in parallel, and switch over on launch day. This works approximately never. The new system invariably under-scopes some legacy functionality, the parallel run uncovers data divergence too late to fix cleanly, and the launch day either does not happen or happens with regressions.
Avoid by: Refusing to scope a rewrite. Always scope incremental replacement, even if the eventual outcome is "every line is replaced".
Failure mode 2 — Stalling at 70%
A team migrates 70% of the legacy functionality, then stalls. The remaining 30% is too painful to migrate; the legacy system stays running for "just one more quarter". A year later, both systems are still running.
Avoid by: Setting hard delete dates for legacy code at the start of the project. If you commit to deleting the auth subsystem by Q3, you build the migration to make that deletable by Q3.
Failure mode 3 — Operational rejection
The new system ships on time and on budget. The team refuses to use it because it is unfamiliar, the runbook is incomplete, or the support model changed. Within six months, the team is back to running the legacy system "as a backup".
Avoid by: Training continuously, transferring runbook ownership before launch, and treating the team's discomfort as a build issue, not a behavior issue.
A worked example: a 12-month timeline
Here is what a real 12-month legacy modernization looks like for a medium-sized professional services firm migrating off a 15-year-old case management system:
Months 1-2: System mapping. Façade design. Surface inventory. Operating runbook capture.
Months 3-4: Build the façade. Migrate the first read-only report to the new stack as a proof of pattern.
Months 5-7: Migrate document storage and retrieval (the most-touched read path; modest write volume; easy to validate). Dual-read and shadow compare for 30 days. Cutover.
Months 8-9: Migrate matter creation and case-status workflow. Dual-write, dual-read, careful monitoring. Cutover.
Months 10-11: Migrate billing integration (the riskiest write path — last, not first). Dual-write for 60 days because the financial blast radius justifies extra caution.
Month 12: Delete legacy code. Migrate authentication as the final cutover. Decommission the legacy infrastructure.
Throughout: weekly team demos, monthly partner check-ins, quarterly pricing reviews to make sure scope creep has not crept.
A project of this size typically runs $250-450k all-in. Larger consultancies will quote 2-3x. We have shipped four projects of roughly this shape.
Where to start
If you are sitting on a legacy system and not sure whether to modernize:
1. Run the mapping (step 1) regardless. It is useful operational documentation independent of any modernization.
2. Get one outside opinion on whether the system is worth modernizing or worth replacing entirely. Some legacy systems are at the point where a focused SaaS or modern off-the-shelf platform is the right move.
3. If modernizing is the call, scope the first three months — façade plus one feature — as a discrete engagement before committing to the full year.
For a 30-minute walkthrough of your specific situation, book a consultation. We have done this often enough to give you a same-day read on which of the three failure modes is the biggest risk for your situation, and how to mitigate it.
See also our service page on legacy system modernization for the engagement-level framing.