Legacy software modernization: rewrite or replace step by step? A seven-question test
C.E.O. Bee Engineering22 min read
In legacy software modernization, "rewrite or replace piece by piece?" has no single right answer: incremental migration is needless overhead for a small, simple system and usually the safer path for a large one that keeps changing. This article tests the direction with seven questions: intercepting requests, code access, size, shutdown speed, natural boundaries, proof of today's behavior and a changing business. Then come the first slice, data, cutover and rollback, and a copy-ready slice plan. General information; scope is settled in a written quote.
Legacy software modernization: "rewrite it?" is two separate decisions
If you own or run IT for a system that has worked for years, you have two questions that usually get asked as one. First: does this system need to be touched at all? Second: if it does, should it be rewritten from scratch or replaced piece by piece? The first is an audit question; the commands, the 0-to-2 scoring matrix and the report skeleton in how to take over an unfinished software project answer it, and we do not repeat them here. This article takes the second: once a direction has been chosen, how a legacy system is sliced and how each slice is put into service. Knowing your own answer before you ask for a custom software development quote makes the quotes easier to read.
The two extreme positions are well known, and the unfinished-project article covers them briefly in the context of an audit result; here we take only the point this article rests on. In 2000 Joel Spolsky called rewriting working code from scratch the single worst strategic mistake a software company can make (Spolsky). Martin Fowler writes that the simple-sounding plan of building a new system that does exactly what the old one does, in a newer technology, has gone "down in flames most of the time" (Fowler). Neither says that incremental replacement works for every system, though; Microsoft's documentation explicitly lists the cases where it may not be suitable. That is why the choice of direction can be written as a test: seven questions, each with its basis.
We describe how we run assessment, roadmap and rebuild work on our software consulting and modernization page.
Four paths, one table
First, the options by name. A source name in a cell points to the page the statement rests on; cells marked "inference" or "judgment" go beyond what the source writes and are our own engineering judgment, not a standard.
| Path | What it does | When it fits | Main risk | Way back |
|---|---|---|---|---|
| Rewrite (big bang) | Builds the new system from scratch and switches over in one step when it is ready | The system is small and replacing all of it is simple (Microsoft Learn says incremental migration may not be suitable in this case; that a rewrite then fits is our inference) | Defining and agreeing on the existing behavior can be a big job in itself, and the plan turns into one large cutover (Thoughtworks) | If the old system is switched off at cutover, a way back has to be built separately (judgment) |
| Incremental replacement (strangler) | Routes requests between the old and new systems through a facade (proxy), moves functionality over slice by slice, and finally retires the old system (Microsoft Learn) | Large systems, critical components or complex features are being replaced, and the old system can stay up throughout the transition (Microsoft Learn) | The facade can become a single point of failure or a bottleneck; the temporary architecture has a cost (Microsoft Learn) | Pointing the routing back at the old system (judgment); for data, rollback to the old database stays possible until the domain tables and synchronization processes are removed from it (Microsoft Learn) |
| In-place internal improvement | Decouples the system with small, reversible changes: expand the interfaces first, migrate the consumers, then contract the old one (Parallel Change) | There is no natural seam to slice along yet; you open one first (judgment) | If the behavior is not written down and there are no tests, every change is risky (judgment) | Each step is small and separate, so a single step can be reverted (judgment) |
| Wrapping without touching | Puts a new layer around the old system without touching it, and freezes the old one (judgment) | There is no access to the source code or to an integration point you can change (Microsoft Learn says incremental migration may not be suitable in this case; that wrapping is the option here is our inference) | The old system's problems stay in place (judgment); the new layer may inherit the old system's concepts (Microsoft Learn: without an anti-corruption layer, calls can force the new system to adopt legacy conventions) | Removing the new layer is enough; the old system is unchanged (judgment) |
What each path costs in total is a separate question; the worksheet in off-the-shelf or custom software: total cost of ownership shows how to work out the line items. It was written for comparing off-the-shelf and custom software, so adapting it to a modernization is up to you.
The decision test: seven questions
Answer each question for your own system with "yes", "no" or "I don't know". "I don't know" is also a result: finding the answer becomes the first job of the migration.
| # | Question | If yes | If no | Basis |
|---|---|---|---|---|
| 1 | Can you step in between requests and route them? | A facade (proxy, API boundary, queue) can be built; incremental migration is possible | The pattern may not be suitable; first open a point where you can intercept | Microsoft Learn |
| 2 | Do you have access to the source code, or to an integration point you can change (a database, for example)? | You can disable migrated functionality in the old system and redirect internal calls | Only wrapping from the outside is possible; who owns the code and how it is handed over is a separate matter | Microsoft Learn |
| 3 | Is the system small, and is replacing all of it simple? | Incremental migration may be unnecessary overhead; a rewrite is worth considering | The risk of a rewrite grows | Microsoft Learn |
| 4 | Will the old system be shut down completely and quickly? | The investment in an intermediate architecture may not pay off; the cutover and shutdown plan comes first | An intermediate architecture can be a reasonable investment | Microsoft Learn |
| 5 | Can you find a natural boundary: a separate function, a separate data owner, a separate rate of change? | Slices can be released one by one | First open a boundary through internal improvement | Fowler; Parallel Change |
| 6 | Do you know today's behavior in writing, and can you prove it with tests? | Each slice's acceptance can be measured against the old behavior | First pin the behavior down: characterization tests and a recording of real traffic | Thoughtworks; Feathers |
| 7 | Will the business keep changing during the migration? | A big bang means chasing two moving targets; each slice fixes its own target | The risk of a rewrite is lower | Judgment; Thoughtworks |
The "Basis" column shows which page each question comes from; the consequences in the "If yes" and "If no" columns are our inference. The first four questions come from the Microsoft Learn document that lists the situations where incremental migration may not be suitable. The fifth rests on Fowler's observation: well-decoupled systems are rare, and you usually create the decoupling afterwards. Unlike the first four, the sixth question does not come from the Microsoft Learn document; it rests on an observation behind the Thoughtworks team's work and the characterization-testing approach: whether today's behavior is written down. The Thoughtworks team (Cartwright, Horn, Lewis) writes that for plans aiming to reproduce the old behavior exactly, even defining and agreeing on the existing behavior is a big job, and that it leads to one large cutover (Patterns of Legacy Displacement). To record the behavior of untested code as it is, characterization tests are used; for the audit side, see the takeover article again. The seventh question is our judgment; the same source also describes how the gap between the requirements accepted at the start of a long program and the business need widens over time.
Now let us turn the answers into a direction. Read the table from top to bottom; the first row that fits gives the direction, and the last two rows are conditions added to it. This ordering is our engineering judgment, not a standard.
| Answer pattern | Direction |
|---|---|
| 4 yes | A cutover and shutdown plan: write down how and against what you will retire the old system, without investing in an intermediate architecture |
| 3 yes, 4 no | Consider a rewrite; the system is small and easy to replace |
| 2 no | Wrap from the outside only; first settle code access and the question of rights |
| 1 or 5 no, 2 yes | Internal improvement and opening a boundary first; incremental migration after that |
| 1, 2 and 5 yes; 3 and 4 no | Incremental migration |
| 6 no (regardless of the other answers) | Whatever the direction, pin the behavior down first |
| 7 yes (regardless of the other answers) | If a rewrite is chosen, approach it by slicing: pieces that fix their own targets instead of one big cutover locked to a single target |
A hypothetical reading: say your answers are 1 yes, 2 yes, 3 no, 4 no, 5 no, 6 no, 7 yes. The row "1 or 5 no, 2 yes" fits: first open a boundary through internal improvement, then migrate incrementally. Because you said no to question six, the first step is to pin the behavior down; because you said yes to question seven, once the boundary exists you plan slices with separate targets instead of one big cutover. In a real system some answers will come out as "I don't know"; finding the answer to that question is the first job.
Choosing the first slice
The Thoughtworks team starts slicing by looking at how the existing solution maps to business capabilities: where one big technical solution serves several needs, can one need be separated and delivered independently, with the pieces depending on each other as little as possible. Microsoft Learn adds that high-return changes can be made before low-return ones. A table of criteria that combines the two ideas is our judgment:
| Criterion | Why | For the first slice | For later slices |
|---|---|---|---|
| Clear boundary (separate function, separate data) | A part with few dependencies can be released on its own | Required | Relaxes as the boundary widens |
| Independently measurable value | You measure the result against the old system; value arrives early (Fowler) | Required | Required |
| Owns data with one clear writer | No dual-write or consistency risk | Required | If data is shared, the rules in the data section apply |
| High rate of change | Starting where the return is high (Microsoft Learn: high-return replacements before low-return ones; that frequency drives the return is our assumption) | Preferred | Moves to the end as the rate drops |
| Low cost of failure but instructive | The first slice teaches the method; Fowler describes learning that grows with each slice | Preferred | Slices with a high cost of failure come later, with what was learned |
Slicing has another side: measuring what you are about to change. In the system described in the low-latency live market data engine article, the load was produced by replaying real connection traces instead of generating synthetic traffic; the sources of the problem were found by measurement, and the rewrite that followed, described in the article as "four parts," focused on the fan-out tier the measurement pointed at. That is a newly built system, not a migration of an old one; we do not repeat the article's figures here. The principle we borrow: do not replace the whole system, replace piece by piece the place the measurement points at.
Data: the hardest part
Slicing code is comparatively easy; slicing data is not. Four rules keep the work manageable.
Expand, migrate, contract. Danilo Sato's Parallel Change pattern splits a backward-incompatible change into three phases: first you expand so that the old and new forms are supported together, then you migrate the consumers one by one to the new, and last you remove the old (Sato). The adaptation to a schema is ours: first add the new column or table and leave the old one alone; move every component that reads and writes to the new one; when no consumer is left, remove the old one.
Shared data and a single writer. Microsoft Learn asks you to think ahead about how to manage the data stores that both the old and new systems may use, and to make sure both systems can reach those resources at the same time. If the old and new systems write to the same record, dual writes create a consistency problem; our judgment is to name one writer for each piece of data.
A translation layer. During the transition the two systems call each other. Microsoft Learn recommends an anti-corruption layer to translate these calls: it protects the design of the new system from the concepts of the old one; without it, the new system may be forced to inherit the old one's rules.
Extract, synchronize, verify. In Microsoft Learn's database example, the relevant tables are first loaded into a new domain database with ETL, kept in sync with change data capture (CDC), and the consistency between the two databases is verified before the cutover. Returning to the old database is possible while the old objects and the synchronization processes are still running; once they are deleted, returning requires rebuilding the objects and replaying the changes, which significantly increases effort and risk. For that reason the document says to treat the removal of the old objects as a deliberate last step taken after the new system is verified. We do not recommend a product; the principle is tool-independent.
To check consistency often and cheaply, comparing counts and digests between old and new (record counts, totals) is enough. In the market-data article, during a long endurance test, a cheap consistency check exposed a leak long before it would have turned into a memory alarm; again not a migration, but an example of the same principle.
Cutover and rollback
A cutover is the moment a new slice is opened to real users. The Thoughtworks article names cutover strategies; below are four of them, side by side with our reading note and a rollback column.
| Method | When it fits | How to roll back |
|---|---|---|
| Dark launching | Running the new back-end behavior with real calls without showing it to users, to measure load and performance impact; calling old and new code together, comparing the results and returning a single response to the user (Fowler) | Switch the new path off with a feature flag (Fowler) |
| Canary release | Opening a change gradually to a small subset of users before opening it to everyone (Sato) | Route users back to the old version (Sato) |
| Divert the flow | When a shared function that collects data for many systems (such as reporting) exists, separate it from the old system first; gradual migration and parallel running (Thoughtworks) | You can go back to the old system as long as parallel running continues and the old application has not been shut down (judgment) |
| Stop the world cutover | Stopping normal business operations and switching to the new system (Thoughtworks) | A rehearsal and a rollback plan must be written before the cutover (judgment) |
Whichever method you choose, the rollback criterion is written before the cutover: which indicator going wrong, who decides, where you go back to. Entering a cutover without a written criterion creates an argument at the moment of decision. In the context of Divert the Flow, Thoughtworks describes leaving parallel running open for a period and raising an alert if the old and new applications diverge too much as an extra safeguard; that is the way to run old and new side by side and compare them. The market-data article also shows that measuring by replaying real behavior works.
The intermediate architecture built for the transition is temporary. Fowler writes that the code built for it goes away when the modernization is done and that the reduction in risk outweighs the cost; Microsoft Learn notes that the facade should not become a single point of failure or a bottleneck, and that when the migration ends it is generally removed or may be kept as an adapter for old clients. So every slice's plan has a closing line: which condition, once met, takes the intermediate architecture down.
Migration slice plan template
Copy the two tables below and fill in one row per slice in each, under the same slice name. An empty cell is a question; a cell you cannot fill shows that the slice is not ready. You choose the slice names and the order.
The first table says what the slice is and what it rests on.
| Slice | Boundary (seam) | Data it owns | Dependencies | Proof of old behavior (test, recording, replay) |
|---|---|---|---|---|
The second table says how the slice goes into service and how it is closed out.
| Slice | Cutover method | Rollback criterion (indicator, decision-maker, target) | Acceptance criterion | Intermediate architecture and teardown condition | Status |
|---|---|---|---|---|---|
How to use them: fill in the boundary and data columns of the first table first, because both determine the choice of the first slice. If the proof of old behavior is empty, the slice stays "unknown" in the plan and the pinning work comes first. The acceptance criterion states in writing how the slice will be approved against the old behavior; how to write that line in a quote is shown by the "deliverables and acceptance criteria" item of the scope-document template in how to read a software proposal; apply it as one row per slice. After each slice, update the closing line.
The third table, the pre-cutover check, is filled in right before the cutover. The rows are our framework; they contain no numbers or durations.
| Pre-cutover check | Yes / no / unknown | Owner |
|---|---|---|
| Has the rollback path been tried? | ||
| Is the rollback criterion written down (indicator, decision-maker, target)? | ||
| Is the old system closed to writes for this data, and is the single writer clear? | ||
| Are the count and digest comparisons between old and new done? | ||
| Does the proof of old behavior (test, recording) pass on the new slice? | ||
| Are monitoring and alerts ready? | ||
| Is the facade built so that it is not a single point of failure? | ||
| Is deleting the old objects planned as a separate, later step after the cutover? | ||
| Who makes the cutover decision, and who is informed? | ||
| Is the closing line (the teardown condition for the intermediate architecture) written down? |
When a rewrite is the right call
Rewriting old software is not bad; an unplanned, big-bang rewrite is risky. Microsoft Learn writes that incremental migration may not be suitable when the system is small and replacing it is simple; a rewrite may be reasonable then (our inference): a system with clear behavior, few external dependencies and a clear owner. Spolsky's article makes the same distinction: throwing away a function or refactoring a class is fine; throwing away the whole program is dangerous. The Corporate Website Redesign card in our portfolio describes rebuilding a corporate website, not a software migration; it says "we keep meeting the same site with pricing buried three menus deep, running on a stack too fragile to touch" and adds "We rebuild these on a modern stack." Those sentences only show that we, too, choose to rebuild from scratch for this kind of work; which path fits your system is decided by the test above. For the question of which layer to rebuild in an app written quickly with AI, see the audit in is your vibe-coded app production-ready.
If you want the decision turned into a written assessment and roadmap, see our software consulting and modernization page.
How we do it
We repeat here only what we have written on our site. Every engagement is a paid project that starts with a written quote you approve. A clear scope, timeline and cost are in writing before anything is billed, and nothing is charged until you approve.
Every technical decision is written down with its reasoning, and you talk directly with the engineer writing your code. Platforms move: dependencies get deprecated, APIs change, security advisories land; on the projects we look after, we keep an eye on what yours depends on and raise what needs attention. Ownership of the delivered work transfers on full payment, as set out in our terms, and if you ever want your infrastructure moved to another team, we help you transfer it cleanly with accounts, domains and documentation. The answer to taking over a project another company left behind is on our custom software development page, and the steps are on the Process page. Tell us about your project.
Frequently asked questions
When is rewriting legacy software the right call?
When the system is small, replacing all of it is simple, its behavior is clear and its external dependencies are few. Microsoft Learn writes that in this case incremental migration can be unnecessary overhead. If the system is large and the business keeps changing during the transition, a one-step rewrite means chasing two moving targets.
What is the strangler fig pattern, and does it apply to every system?
It is the approach of moving functionality to the new system slice by slice through a facade and retiring the old system at the end; the name comes from a kind of vine Fowler saw, and it is a pattern used in legacy software modernization. It does not apply to every system. Microsoft Learn writes that it may not be suitable in four cases: when you cannot intercept requests, when you have no access to the source code or to an integration point you can change, when the system is small and replacing it all is simple, and when the old solution has to be shut down completely and quickly.
Can new features be added while the old system runs, and does the system have to go down during the migration?
In the incremental approach the old system stays up and the facade routes requests. Fowler describes the approach as often starting with small additions built on top of, but separate from, the old system, mostly new features. Whether a shutdown is needed depends on your cutover method: with "stop the world," normal business operations stop by definition; with the incremental methods, the old system stays up.
How is data migrated, and is a rollback possible?
In the order expand, migrate, contract: add the new structure, move the consumers, remove the old one last. Name one writer for each piece of data and verify the consistency between old and new before the cutover. A rollback is easy while the old database and the synchronization processes are still running; after they are deleted it is expensive and risky (Microsoft Learn). This is a general description of a method; a plan specific to your database becomes clear in a written quote together with the scope.
Is it possible to leave the old system completely untouched and only put a new layer around it?
If you have no access to the code and the integration points, this is usually the only way; a translation layer is needed to protect the new layer from the old system's concepts. The cost: the old system's problems stay in place. This is our engineering judgment; for access and handover questions, see the takeover article.
Sources
The pages below were opened and read on November 6, 2026. This article is general information. Pages may change over time.
- Ian Cartwright, Rob Horn, James Lewis, Patterns of Legacy Displacement (Thoughtworks; March 5, 2024): Feature Parity, cutover strategies; also Divert the Flow (January 20, 2022).
- Microsoft Learn, Strangler Fig pattern (last updated June 2, 2026, as shown on the page): cases where it may not be suitable, facade, anti-corruption layer, database example.
- Martin Fowler, Strangler Fig (August 22, 2024) · Dark Launching (April 29, 2020).
- Danilo Sato, Parallel Change (May 13, 2014) · Canary Release (June 25, 2014).
- Joel Spolsky, Things You Should Never Do, Part I (April 6, 2000).
- Michael Feathers, Characterization Testing (August 8, 2016).
— Related serviceSoftware Consulting and Modernization