Skip to content
SolutionPlus
All articles
Product ModernizationLegacy SystemsEngineering Strategy

Rebuild, refactor, or wrap: how we decide what to do with legacy software

Rebuild vs refactor vs wrap: a builder's decision guide to legacy modernization, the four questions that settle it, and what each path costs in time and risk.

8 min read

TL;DR

Most legacy systems should be refactored or wrapped, not rebuilt. The decision comes down to four questions: how often the system changes, how large its integration surface is, who still understands it, and how much downtime the business can tolerate.

The most expensive sentence in software is a short one: let's just rewrite it.

We hear it a few times a year, usually from a team that has spent eighteen months being annoyed by a system that still, mostly, works. A good share of our work at SolutionPlus is taking over exactly those systems: live, aging, revenue-critical, and hated. We run a German-led engineering organization with a senior team in Pakistan, delivering in English and German, and product modernization is one of the things clients hand us most. So we have opinions about the rewrite sentence. The main one: full rewrites are usually ego, not engineering. Joel Spolsky made the canonical case against the big rewrite back in 2000, and twenty-six years of watching teams ignore it has only made the essay more accurate.

Sometimes a rebuild is right. It is right far less often than it is proposed.

The three paths (and the fourth nobody mentions: strategic neglect)

Every modernization conversation is a choice between three moves, plus one that never gets written on the whiteboard.

Rebuild means throwing the implementation away and writing a new one, usually on a new stack, while the old system keeps running until cutover. It produces the cleanest end state and the longest period where you pay for two systems and get value from one. When it is genuinely right, the system is small, well understood, and narrow at the edges: the kind of thing a team doing web application development can re-implement in a quarter without archaeology.

Refactor means keeping the system and improving it from the inside. Modules get extracted behind tested interfaces, and the framework moves up version by version instead of in one heroic jump. Most long-lived systems are what Foote and Yoder called a big ball of mud back in 1997; refactoring accepts that and improves the structure you actually have. It is slower to feel and faster to ship, and it never interrupts the business.

Wrap means leaving the old core alone and putting new code around it: an API facade, a modern front end, an integration layer that translates between the legacy engine and everything new you build. You get a modern surface on an old motor.

The fourth path is strategic neglect: deciding, on purpose, to leave the system untouched and spend the money elsewhere. Nobody proposes it in a meeting, because it sounds like giving up. For a stable system that changes twice a year and just works, it is frequently the correct answer, and an honest modernization partner should rule it in or out before selling you anything.

The questions that decide it

When we run discovery, we don't start with the code. We start with four questions. The code review comes later; these answers constrain it.

How often does the system change?

This is the strongest signal. If the business asks for changes every week and each one takes three weeks to ship, the system is actively taxing the company, and the tax compounds. That pushes you toward refactor or rebuild. If the system changes twice a year and each change is small, the tax is real but tiny, so wrap it or leave it alone. We once talked a client out of rebuilding a reporting tool that had seen four change requests in the previous year. They kept the tool and spent the budget on the product that actually made money. That was a good engagement, even though the invoice was smaller.

How big is the integration surface?

Count everything that talks to the system: the ERP, the CRM, the payment provider, the shipping API, the spreadsheet some accountant exports every Friday and feeds into three other tools. Every integration is a contract. A rebuild renegotiates all of them at once, and the ones nobody documented are the ones that break at cutover. A wide integration surface argues against rebuilding and for wrapping or incremental migration. A narrow one, two or three documented APIs, keeps every option open.

Who still understands the system?

Legacy systems encode business rules that exist nowhere else. Michael Feathers opens Working Effectively with Legacy Code by defining legacy code as code without tests, and the deeper problem is that the code is often the only documentation the business has. If the person who understood the billing logic left in 2022, a rewrite means rediscovering those rules by trial and error. You will rediscover some of them from angry customers. That is the hidden line item in every rebuild estimate, and it is why refactoring, which keeps the rules working while you write the tests that finally record them, is the safer default.

How much downtime can the business tolerate?

"We'll just switch over one weekend" is a red flag we hear on sales calls. If the honest answer to downtime is none, big-bang cutover is off the table, which eliminates most rebuild plans as usually drawn and pushes you toward incremental migration. If the business can absorb a maintenance window, more options stay open. Get the answer in writing before you price anything.

What each path costs in time and risk

The systems we take over are usually a decade or more old and a framework version or two behind, and the cost shapes below come from those engagements.

Wrapping is measured in weeks. In our engagements, a facade plus a new front end on a stable legacy core has taken four to ten weeks. The risk is that you inherit everything underneath: the wrapper makes the system pleasant to use and does nothing about the system being expensive to change. Treat it as buying time, not fixing the problem.

Refactoring is measured in months. In our modernization engagements, a mid-size business application has run three to nine months, done alongside normal feature work rather than instead of it. The risk is discipline. Refactoring dies by a thousand postponed cleanups, so it needs a team that treats the cleanup as the work, not as a hobby after the work.

Rebuilding is measured in quarters: six to eighteen months for anything with real business logic, going by the rebuilds we have been brought in to quote or to rescue. The initial estimate rarely holds, and the reason is the last ten percent. The export format one customer depends on. The pricing rule from 2019 that nobody remembers but everyone bills by. The first ninety percent of a rewrite is the easy part, which is why rewrite demos always look great in month two.

One client came to us with a billing platform a bit over ten years old, two framework versions behind, deploys that took half a day, and a release cycle that had stretched to monthly. A rebuild was on the table and would have taken the better part of a year. We refactored instead. The deployment pipeline was automated first so releases were safe, then the invoicing engine came out behind a tested interface, and the framework went up in steps. Release lead time on that system went from weeks to days, and the business never stopped selling. Our case studies cover similar engagements in more detail.

Our engagements tend to fall into a few shapes. A stabilization sprint, two to four weeks, stops the bleeding: tests around the riskiest paths and a deploy pipeline that no longer depends on one specific person's laptop. An incremental modernization retainer gives you fixed monthly capacity that carves and refactors alongside your roadmap. For teams that want the whole thing operated, we run a build-operate-transfer model where we modernize the system, run it, and hand back a team that owns it. And sometimes the engagement ends with us telling you to do nothing, which loops back to strategic neglect.

The migration pattern we reach for most

When we do migrate, the pattern we reach for most is the strangler fig, named by Martin Fowler after the vine that grows around a host tree until the tree is gone. In practice it works like this. You put a routing layer in front of the old system. New capabilities get built in the new system, behind the router. One at a time, you carve existing capabilities out, rebuild them, and flip the route. When traffic to an old module hits zero, you delete it. There is no cutover weekend, because there is no cutover.

A second client ran an order management system sitting next to their ERP, and the business could not stop taking orders, ever. We wrapped it first so new work could happen against a clean API, then strangled it over roughly a year: customer management first, then pricing, then the order workflow last because it was the riskiest. The old system shrank instead of exploding. Total downtime across the whole migration was zero, which is the outcome this pattern exists to buy.

The trade-off, stated plainly: for most of that year the client paid to run two systems plus a routing layer, and the router itself is code you have to maintain and eventually delete. For a small system, the facade can cost more than just rewriting the thing. The strangler fig earns its overhead when downtime is unacceptable and the system is too big and too wired-in to rewrite in one swing. When those conditions aren't true, we say so and quote the simpler job.

The decision is mostly about the business

Here is the threshold we apply. Count what the business asked the system for last year, including the requests it gave up on because the system could not absorb them. If that count is low, as it was for the reporting tool with four change requests in a year, leave the system alone and spend the budget where it earns. If the refused changes are what is holding the product back, the system is taxing you, and the answers above tell you which path removes the tax. If you want a second pair of eyes on the count, talk to us. The answer might be a rebuild. It usually isn't.

Frequently asked questions

Should we rebuild or refactor our legacy system?

Refactor by default. Rebuild only when the system is small, well understood, narrowly integrated, and the business needs changes the current architecture cannot absorb. In our modernization work, rebuilds are the minority of recommendations; the honest answer depends on change frequency, integration surface, remaining team knowledge, and downtime tolerance.

Can a legacy system be modernized without downtime?

Yes, and for most live business systems it has to be. The standard approach is the strangler fig pattern: a routing layer in front of the old system, capabilities carved off and rebuilt one at a time, old modules deleted when their traffic hits zero. There is no cutover weekend because there is no cutover.

How long does legacy modernization take?

In our modernization engagements, wrapping a stable legacy core behind an API facade has run four to ten weeks. Incremental refactoring of a mid-size business application runs three to nine months alongside normal feature work. A full rebuild of a system with real business logic runs six to eighteen months, and the initial estimate rarely holds.

What does a modernization engagement cost?

It depends on the path, which is why discovery comes first. Our engineering rates sit in the $25–49/hr band, and small stabilization sprints are scoped as fixed packages. A wrapper project and a full rebuild differ by an order of magnitude, so the four decision questions matter more than the rate card.

Want the next pattern worth chasing?

We publish weekly. Get in touch and opt into our updates — we’ll let you know when the next article lands.

Get updates