Application Modernization Without a Big-Bang Rewrite | Deployed

Application Modernization Without a Big-Bang Rewrite

Cloud & DevOpsPublished: January 6, 20268 min read

Every ageing system eventually produces the same proposal: rewrite it properly this time. The reasoning is always sound — the current codebase is slow to change, the framework is unsupported, nobody understands the payment module.

The problem with rewrites is not ambition. It is that during the rewrite, the business still needs changes to the old system, so you maintain two codebases, and the new one has to reach feature parity with a moving target before it delivers any value at all.

Incremental modernisation is less satisfying and much more likely to finish. The core idea: every step must ship, and the system must be better after each one even if you stop there.

"Legacy" bundles several distinct problems with different solutions, and naming yours prevents solving the wrong one:

  • Unsupported runtime or framework. A security and hiring problem. Solved by upgrading, which is bounded work.
  • Poor structure. Changes are risky because everything is coupled. Solved by refactoring behind tests, incrementally.
  • Wrong architecture for current load. Solved by targeted extraction of the parts that need to scale.
  • Nobody understands it. A knowledge problem. Solved by documentation and characterisation tests, not by rewriting.
  • The business model changed. The only case where a rewrite is sometimes genuinely right, because the data model no longer matches reality.

Teams frequently propose a rewrite for the first or fourth problem, where it is by far the most expensive available option.

You cannot safely change a system whose behaviour you cannot verify. Before any restructuring:

Characterisation tests

Not tests of what the system should do — tests of what it currently does, including the odd behaviours. Their purpose is to detect change, and they are what makes refactoring safe rather than hopeful. Where the current behaviour is clearly a bug, note it and keep the test; fixing it is a separate, deliberate decision.

A staging environment with realistic data

Anonymised production-shaped data, not fixtures. Legacy systems break on the messy real records — the account created in 2014 under rules that no longer exist.

Observability on the current system

You need a baseline. Error rates, latency, and throughput before you change anything, or you will not be able to tell whether a change helped.

This phase feels like delay. It is the thing that makes everything after it possible, and skipping it is why incremental efforts turn into rewrites by accident.

The general approach is well known: put a routing layer in front, move functionality piece by piece, retire the old code as it is replaced. The practical details determine whether it works.

Start at the edges

Choose a piece with few dependencies and clear boundaries — notifications, reporting, file processing, a public API. Not the core domain. Early wins prove the pattern and build confidence.

Route at a stable boundary

URL paths or API endpoints work well. Routing on database tables or internal function calls creates a coupling that becomes its own problem.

One direction of data flow

The most common way this fails is both systems writing to the same data with different assumptions. Pick an owner for each piece of data. If both need to read it, one owns writes and the other reads — never both writing.

Retire deliberately

Delete the old path once the new one is proven. Systems that keep both alive "just in case" end up permanently maintaining two implementations, which is worse than where they started.

Modernisation competes with features for budget, so it has to produce visible value continuously. The order that works:

  1. Upgrade the runtime and dependencies in place. Unglamorous, bounded, and it removes the security argument immediately.
  2. Add tests and observability. Enables everything else.
  3. Extract the piece causing the most operational pain. Whatever wakes someone up or blocks releases. Visible relief buys goodwill for the rest.
  4. Extract the piece blocking the most roadmap work. Now modernisation is visibly accelerating features.
  5. Keep going while the return justifies it — and stop when it does not.

That last point matters. The goal is not a fully modern system; it is a system that is not in the way. Plenty of well-run products have an old, stable module nobody touches, and that is a perfectly good outcome.

To be fair to the option, a rewrite is defensible when several of these hold at once:

  • The data model fundamentally does not match what the business now does.
  • The system is small enough to rebuild in a few months, not years.
  • The requirements are genuinely well understood, because you have been running them for years.
  • You can freeze changes to the old system during the rebuild.
  • The technology is not merely old but actually unobtainable — no support, no hosting, no hiring pool.

If you cannot freeze the old system, that alone is usually decisive. Chasing a moving target is what turns a six-month rewrite into a two-year one.

Track one number through a modernisation programme: how long it takes to ship a small change safely, from request to production.

It captures what people actually mean when they say a system is legacy, it is visible to non-technical stakeholders, and it improves incrementally rather than only at the end. If that number is falling, the programme is working — regardless of how much old code remains.

Working around a system instead of with it?

We modernise applications incrementally, keeping the business running while delivery speed improves step by step.

Explore application modernization

Frequently asked questions

Why do full rewrites so often fail?

Because the business still needs changes to the old system during the rebuild, so you maintain two codebases while the new one chases feature parity with a moving target — delivering no value until the very end.
+

What has to happen before any restructuring?

Characterisation tests that capture what the system currently does, a staging environment with realistic anonymised data, and observability to establish a baseline. Skipping this is how incremental efforts turn into rewrites by accident.
+

Where should we start extracting?

At the edges — notifications, reporting, file processing, or a public API. Something with few dependencies and clear boundaries, not the core domain. Early wins prove the pattern and build the confidence to continue.
+

How do we measure progress?

Track how long it takes to ship a small change safely, from request to production. It captures what people mean by 'legacy', is visible to non-technical stakeholders, and improves incrementally rather than only at the end.
+

SHARE

SUMMARIZE WITH AI

Upcoming Webinar

Cybersecurity for Business Impact: Protecting Operations from AI-Powered Threats

June 29, 2026 10:00 am EST

00 Days
00 Hours
00 Minutes
00 Seconds