// Software Development

Modernising a Legacy Application Without a Big-Bang Rewrite

Why full rewrites usually fail, and how the strangler approach lets you replace an old system piece by piece while the business keeps running.

Modernising a Legacy Application Without a Big-Bang Rewrite

Almost every company has one: the system that runs the business, that nobody dares touch, written by people who left years ago. It is slow to change, hard to hire for and risky to deploy. The tempting answer is a complete rewrite. It is also the answer that fails most often.

Why big-bang rewrites fail

  • The old system keeps changing while the new one is being built, so the target moves.
  • Hidden behaviour. Years of fixes and special cases live in the code and nowhere else. The rewrite misses them, and customers notice.
  • No value until the end. The business pays for months, sometimes years, and sees nothing until a risky switch-over day.
  • Two systems to run during the overlap, with twice the cost.

The alternative is to change the system gradually and keep it working throughout.

Big-bang rewrites fail because the target keeps moving.

The strangler approach

The name comes from a vine that grows around a tree until it replaces it. You place a thin routing layer in front of the old application. Then you build new functionality, one slice at a time, and redirect just that slice's traffic to the new code. The old system shrinks until it can be switched off.

flowchart LR
    A["Users"] --> B["Routing layer"]
    B -->|"Shrinking share"| C["Legacy app"]
    B -->|"Growing share"| D["New services"]
    C -.->|"Features move over"| D

At every stage the product works, each step is small enough to reverse, and the business sees improvements early.

Step by step

1. Understand what you have

Before changing anything, map the system: its main features, its data, the other systems it talks to and who uses which part. Even a rough picture reveals which parts are tangled and which are separate.

Pick a first slice that is valuable and separable.

2. Write tests around current behaviour

You often cannot tell what the "right" answer is, so capture what the system does today. These characterisation tests record current inputs and outputs. They do not prove the behaviour is correct, they prove you did not change it by accident.

3. Put a routing layer in front

A reverse proxy, API gateway or even a small routing service lets you send some requests to the old system and some to the new. Start by passing everything through unchanged, which proves the layer itself is safe.

4. Choose the first slice carefully

The best first slice is:

  • Valuable: a feature people complain about, or one that blocks new work.
  • Separable: with few ties to other parts.
  • Low-risk: not the payment engine on day one.

A reporting module, a customer portal or a notification feature are common starting points.

5. Deal with the data

Data is the hard part. There are three common approaches:

  1. Shared database for a while: the new code reads and writes the same tables. Quick, but it ties the two together.
  2. Sync and replicate: the new service owns its own data, and changes are copied in both directions during the transition.
  3. Migrate and cut over: move a slice's data once and make the new service the single owner.

Pick per slice, and always keep a way back.

6. Release gradually

Run the old and new paths side by side and compare results before trusting the new one. Send a small share of traffic to the new code, watch error rates and correctness, then increase.

7. Retire what you replaced

Delete the old code once its slice is fully moved. If you skip this step, you end up maintaining both forever.

The other options

A full rewrite is not the only alternative to doing nothing. The usual menu is:

Delete the old code once a slice has moved.

Option What it means Use when
Rehost Move to new infrastructure unchanged You need to leave old hardware quickly
Refactor Clean up inside the existing codebase The structure is salvageable
Replatform Upgrade frameworks or runtimes The platform is out of support
Strangle Replace piece by piece The system is large and business-critical
Rebuild Start again It is small, or the logic is well understood

Combining them is normal. You might rehost first for safety, then strangle over a year.

Measure progress

Pick a few numbers you can track: deployment frequency, lead time for a change, number of incidents, the share of traffic served by new code, build time. Visible progress keeps stakeholders supportive over a long programme.

Pitfalls to avoid

  • Modernising without a goal. "Use a newer stack" is not a business case. Tie each step to faster delivery, lower cost, better reliability or new capability.
  • Creating a distributed mess. Do not split into many small services unless you need to. A well-structured modular application is often enough.
  • Losing the knowledge. Keep the people who understand the old system close to the work.
  • Skipping documentation and monitoring for the new parts.

Conclusion

You do not have to choose between living with a fragile legacy system and betting the company on a rewrite. Put a routing layer in front, protect behaviour with tests, move one valuable slice at a time and switch off the old code as you go. It is less dramatic than a rewrite, and far more likely to finish.

Filed underLegacy ModernizationRefactoringArchitectureMigration