Migrations & System Modernisation

Legacy System Migration
What Are the Real Risks (and How to Avoid Them)

It's rarely bad technology that wrecks it. More often, it's data nobody checked, and integrations nobody remembers.

Patrycja Biała
Adventure-movie style poster: a developer with a flashlight examining old code, with a note reading TODO: Temporary fix, Added 2012, Still temporary

At first glance, everything looks fine.

The system runs, users log in, orders get processed, and the days go by without any major incidents. Since the application is doing its job, it's hard to find an argument for modernising it.

The problem only shows up once the business wants to take the next step. Suddenly shipping a new feature takes weeks, a simple integration requires rebuilding several modules, and every change raises the same question: what else is about to break?

It doesn't matter whether we're talking about old .NET, Java, PHP, Rails, or Node.js. Most systems age in a similar way. Not through one bad decision, but through hundreds of small ones. The code grows more complex, the documentation stops keeping up with reality, technologies lose support, and maintaining the system eats up more and more time and money.

At some point, development stops being the biggest challenge – keeping the system running becomes one.

Before You Rewrite a Single Line of Code: 4 Questions That'll Save You a Heart Attack

The biggest threat to a migration is rarely the technology. Usually it's simply not knowing what "skeletons" are hiding in your old system. Before you solemnly announce the project kickoff, ask yourselves these 4 questions.

  1. Who's going to clean up this data mess?

    Has anyone actually checked how many duplicates and 2008-era records with the address test@test.com you've got lying around? The new system will run faster. It'll just return the wrong data faster, too.

  2. Do you know about ALL the integrations? (Spoiler: you don't)

    An old monolith is like a century-old townhouse. Nobody really knows where all those self-installed pipes actually lead. Forgotten invoicing scripts, side integrations with the CRM some intern bolted on five years ago, mailing webhooks... The worst-case scenario is discovering them a week after switching off the old server, right when finance stops receiving payments.

  3. Can your team actually build with this stack, or are they still watching YouTube tutorials?

    Even the most "exciting" framework at the top of GitHub's trending list will sink a project if the team is learning it on live production code. Migration is a terrible time to pick up a new framework, and production is rarely a good training ground.

  4. What's Plan B when something inevitably blows up?

    Patterns like Strangler Fig are great because they let you migrate a system piece by piece. But what will you do if, after switching over a new module at 5pm on a Friday, the data starts drifting apart? Do you have an emergency rollback option, or will you be firefighting in production all weekend?

If the answer to even one of these questions is "we'll figure it out as we go," pause the migration for a moment. It's far cheaper than discovering the answers in production.

How Do You Reduce Risk? Build Beside It, Not Instead of It

The natural instinct is to rewrite the application from scratch. In practice, projects like that drag on for months, burn through large budgets, and end in one risky, all-at-once cutover. If data or integration problems surface at that moment, they have to be fixed under serious time pressure.

History shows just how costly that can get. In 2018, British bank TSB¹TSB Bank, banking platform migration, April 2018, BBC reporting and Financial Conduct Authority findings. locked millions of customers out of their accounts during a platform migration. A few years earlier, Knight Capital²Knight Capital Group, deployment error, August 2012, SEC filings. lost 440 million dollars in just 45 minutes after a botched software deployment. In both cases, the single cutover moment failed, and the ability to roll back quickly barely existed.

That's why more and more companies are choosing the Strangler Fig Pattern. The name was popularised by Martin Fowler, inspired by the strangler fig tree. It sounds ominous, but in software, it's actually a remarkably polite way to retire a legacy system. Instead of ripping it out by the roots, the new application gradually takes over its responsibilities until the old system can enjoy a well-earned retirement.

In practice, this means both systems run in parallel for a while. A routing layer directs users to the new modules, while the rest of the functionality is still handled by the old application. Both share the same database, so every change can be deployed and verified in stages, without one risky "cutover day."

That's the biggest advantage of this approach. Instead of one big deployment, you get a series of small, controlled changes. If something goes wrong, you just roll back that one module, not the whole migration.

The Devil Is in the Detail

Imagine a company migrating five modules: orders, warehouse, invoicing, reporting, and customer support. Choosing the technology is just the starting point. What actually decides the project's success is usually far less obvious.

  1. Migration order

    The team has complained about the warehouse module for years. It's the worst-written part of the system, and every change takes weeks. The obvious temptation is to start there and finally get it out of the way.

    The problem is that four months later, the warehouse still doesn't work. It turns out to be even more tangled than anyone expected. Nobody outside IT sees any results, and leadership starts asking where the money is actually going.

    Now picture a second scenario. The company starts with invoicing instead. It's technically simpler, but it causes daily pain for finance and blocks integration with the new payment provider. Four months later, it's already running in the new app, wired through the routing layer to the same database as the rest of the not-yet-migrated system. Finance stops fixing errors by hand, users notice the change, and leadership gets its first proof that the project is heading the right way.

  2. Monitoring

    Well before the first cutover, the team turns on logs, tracing, and metrics for the old system. That way, once the new module starts taking traffic, you can compare how both apps behave for the exact same operations. Even small discrepancies, like rounding differences, surface before they ever reach users.

    Monitoring should show up before the migration, not after. Otherwise, your first bug-reporting system becomes... your users.

  3. People

    There's another thing rarely said out loud. During a migration, the team naturally splits into two camps: the ones writing the shiny new code, and the ones still patching the old warehouse module because somebody has to. That second group starts feeling exiled to Siberia while everyone else builds the company's future.

    It's not a technical problem, but it can kill a migration more effectively than a bad framework choice. People leave, motivation drops, and the knowledge of the legacy system, exactly the knowledge you need to retire it safely, walks out the door with them. Rotate engineers between old and new code, and make it clear that maintaining legacy isn't a punishment, just a temporary, critical stage of the project.

  4. Success

    Four months later, the invoicing module is live. The timeline holds, which is satisfying, but the real relief comes when finance goes quiet. Nobody sends a screenshot captioned "What is this figure?!" In software, that silence is the best status update there is: everything's working.

Which Technology Should You Migrate To? Depends Which Fire You're Fighting Today

In practice, there's no single "holy" technology. A good choice comes from business needs, team capability, and just what state your current application is actually in.

Before you start comparing frameworks and reading "best tech stacks of 2026" rankings, answer a few simpler questions.

Will the team actually be able to develop it?

A framework nobody knows how to work with will just become tomorrow's legacy system. Pick something your team already uses, or something you won't struggle to hire for. Even the best technology is worth little if the only expert is whoever presented it at the kickoff.

What does this application actually need to do?

The framework should fit the problem, not the other way around.

Admin panel / CRM Laravel + Filament, Django Ideal for internal systems and CRMs.
Data analysis & AI Python A great choice for data analysis and automation.
Large enterprise system .NET, Spring Boot For large systems that need to scale.
Rich frontend Next.js A modern frontend for demanding applications.

Will the new system have to live alongside the old one for a while?

With the Strangler Fig approach, both systems live side by side for a while and often share the same database. So it's worth checking whether your chosen technology can comfortably handle the existing schema: custom tables, legacy primary keys, and decisions made well over a decade ago.

How much will maintenance cost once the initial excitement fades?

Implementation cost is just the start. Just as important is how much it'll cost to develop the system in two or three years, and how easily you'll find the next developers. An exotic technology can dazzle at first, but its real cost shows up fast at the first hiring round.

Will AI help, or just guess?

Popular technologies like Laravel, Django, or Rails have a big advantage: AI has trained on millions of examples of their code and docs, so it understands their conventions better and suggests correct solutions more often. The more niche the framework you pick, the more often the model will guess instead of help.

The worst decision is rarely picking the wrong framework. Far more often, the problem is choosing a technology that looks great on slides but doesn't fit the people, the project, or how the migration is actually run.

Where AI Actually Helps in Migration (and Where It Falls Flat)

The line "AI writes code" belongs in fairy tales. In practice, AI isn't an architect. It's a very fast, meticulous, never-complaining junior you hand the most tedious work to. There are four specific situations where AI brings real value.

  1. Code archaeology, or "what was the author thinking back in 2012?"

    Instead of spending days wading through code with zero comments, you let AI analyse the project. Within minutes you get a dependency map: what connects to what, where the data flows, and why that one mysterious module still works at all.

  2. Tests for "archaeological artefacts"

    Before you touch anything in a legacy system, you need to know you won't break it. AI can generate tests that show how the system behaves today, so after the migration it's easy to check everything still works the same way.

  3. Repetitive work at scale

    CRUDs, forms, the hundredth admin panel. You build one reference module, and AI generates the rest far faster than manually recreating the same pattern for the tenth time.

  4. Untangling decade-old oddities

    During one migration from ASP.NET to Laravel, the old ASP.NET Identity stored passwords in several PBKDF2 variants, depending on the framework version. Working that out by hand would have taken hours. AI figured out the scheme much faster, and the test was simple: could an existing user log in? They could. It worked.

And where does AI fall flat?

Wherever strategic thinking is required. AI doesn't know which modules to move first, how to split a monolith into sensible chunks, or what the business actually prioritises. It can't infer that from code alone.

So treat AI as a great analyst and an assistant for tedious work. It'll knock out a huge number of repetitive tasks, but a human still needs to hold the wheel.

Sources
← Back to blog