---
title: AI Legacy Code Migration: How It Works in Practice | Remqa
url: https://remqa.com/blog/ai-legacy-code-migration
updated: 2026-10-13
---

[Home](https://remqa.com/) / [Blog](https://remqa.com/blog) / AI Legacy Code Migration: How It Works in Practice

Engineering guide

# AI-assisted legacy code migration: what actually works

How AI-assisted legacy code migration works: characterization tests first, strangler fig steps, AI-drafted changes reviewed by engineers, staged releases.

Remqa · October 13, 2026 · 5 min read

Short answer AI-assisted legacy code migration works best when engineers first lock in current behaviour with characterization tests, map the system, then migrate in small slices using the strangler fig pattern. AI drafts the repetitive changes and engineers review every one before it merges.

- Big-bang rewrites usually run long and miss undocumented behaviour

- Characterization tests come before any code changes

- The strangler fig pattern lets old and new code run side by side

- AI is fast at repetitive rewrites but needs tests and human review to be safe

By the Remqa engineering team · Last updated October 13, 2026

Every company has one: the app nobody wants to touch. It runs on a framework that stopped getting updates years ago, the person who wrote it has left and every change takes three times longer than it should. AI coding tools have made migrating that kind of system much cheaper, but only if they are used inside a process that catches their mistakes. This is how AI-assisted legacy code migration works when it is done carefully, and where it goes wrong when it is not.

## Why the big rewrite usually fails

The tempting plan is to rewrite the whole thing in a modern stack and switch over on one day. It rarely works. The old system contains years of small decisions: edge cases, workarounds for a customer, a rounding rule someone added after an invoice dispute. Most of those decisions are not written down anywhere except the code. A clean rewrite drops them, and you find out one bug report at a time after launch.

AI makes this temptation stronger, because a model can generate a new codebase quickly. Fast output does not change the underlying problem. If you cannot prove the new code behaves like the old code, you have just produced a larger amount of unverified software.

## Step 1: Lock in current behaviour with characterization tests

Before changing anything, write tests that describe what the system does today. Not what it should do, what it actually does. These are called characterization tests. They are the foundation of everything that follows.

- Record real inputs and outputs: API requests and responses, report outputs, files the system generates.

- Cover the paths that make money or touch customers first: billing, checkout, permissions, data exports.

- Keep the odd behaviour in the tests, even when it looks like a bug. Decide later, on purpose, whether to change it.

- Make the tests runnable against both the old and the new code.

This is a good first job for AI. Given a module and a set of recorded inputs, a model can draft many tests quickly. An engineer still reviews them, because a test that asserts the wrong thing gives false confidence.

**Rule of thumb:** if a part of the system has no tests describing its current behaviour, it is not ready to be migrated yet.

## Step 2: Map the system before you move it

Build a map of modules, dependencies, data stores, external integrations and scheduled jobs. Note which parts change often and which have not been touched in years. AI tools are useful here too. They can read a large codebase, summarize what each module does and point out where a function is called from. Treat those summaries as a starting point to check, not as documentation.

The map tells you where the seams are. Seams are the places where you can cut one piece out and replace it without touching everything else: a service boundary, an API, a background job, a single page.

## Step 3: Migrate incrementally with the strangler fig pattern

The strangler fig pattern replaces a legacy system piece by piece. New code grows around the old system and takes over one route, feature or job at a time, until nothing is left of the original. At every point, the system works and can ship.

- Put a routing layer in front of the old system (a proxy, an API gateway or a router inside the app).

- Pick one slice with clear boundaries and good test coverage.

- Build the new version of that slice.

- Run both side by side where you can and compare outputs.

- Route traffic to the new slice, behind a feature flag so you can switch back.

- Remove the old code for that slice once it has run cleanly for a while.

Small steps are what make AI safe to use. A model drafting a change to one module, checked by a test suite and reviewed in a normal pull request, is a very different risk from a model rewriting a whole application at once.

## Step 4: AI drafts, engineers review

In practice, the split of work looks like this:

| Task | AI does | Engineer does |

| Characterization tests | Drafts tests from recorded inputs and outputs | Checks assertions, adds missing edge cases |

| Code translation | Converts a module to the new framework or language | Reviews every diff, checks behaviour against tests |

| Dependency upgrades | Finds deprecated calls and proposes replacements | Reads changelogs, decides on breaking changes |

| Documentation | Summarizes modules and writes first drafts | Corrects and approves |

| Architecture | Suggests options | Decides |

The review is not optional. Models produce code that looks right and compiles but changes behaviour in small ways: a different default, a missing null check, an error that used to be raised and is now swallowed. They also call library functions that do not exist in the version you are using. The test suite catches some of this. Careful human review catches the rest.

## Common migration types

### Framework upgrades

Moving to a new major version of the same framework, or from one framework to another in the same language (for example, an old frontend framework to React). These are good fits for AI-assisted work because the changes are repetitive and well documented. Upgrade in stages. Jumping several major versions in one step multiplies the breakages you have to untangle at once.

### Language upgrades and moves

Older language versions to current ones (Python 2 to 3, older Java or .NET versions to current releases) or moving between languages. The risk is in subtle semantic differences: string handling, integer division, date and time behaviour, concurrency. Characterization tests around these areas matter more than anywhere else.

### Monolith to services

Splitting a large application into smaller services. Do it only where there is a clear reason, such as a part that needs to scale or deploy separately. Splitting for its own sake adds network calls and failure modes without much benefit.

### Scripts and glue code

Cron jobs, spreadsheets with macros and one-off scripts that quietly run part of the business. These are often the riskiest pieces because nobody remembers they exist. Find them during the mapping step. If the data they touch is moving too, plan it alongside the [data migration](https://remqa.com/data-migration).

## Pitfalls to avoid

- **Migrating without tests.** You will not know what you broke.

- **Accepting large AI diffs.** Keep changes small enough that a reviewer can actually read them.

- **Fixing bugs during the move.** Migrate first, then fix. Mixing the two makes it impossible to tell whether a difference is a regression or an improvement.

- **Forgetting the data.** Schema changes and data moves need their own plan and their own checks.

- **No way back.** Every slice should be switchable back to the old code until it has proven itself.

## How Remqa runs code migrations

Our [forward deployed engineers](https://remqa.com/forward-deployed-engineers) run legacy code migrations inside your repo and your release process. Tests come first to lock in current behaviour. AI drafts the changes, an engineer reviews every one and the work ships in small stages behind switches you control. Same process as every migration we run: map, test run, verify, cut over, with the old system kept available until you sign off. If you are moving business systems at the same time, see the [CRM migration checklist](https://remqa.com/blog/crm-migration-checklist). Read more about how we handle [code migration](https://remqa.com/code-migration).

FAQ
## Questions people ask.

**Q: Can AI migrate a legacy codebase on its own?**
Not safely. AI can draft tests, translate modules and propose dependency fixes quickly, but an engineer needs to review every change and a test suite needs to confirm behaviour has not changed.

**Q: What are characterization tests?**
Tests that record what a system does today, including odd behaviour, rather than what it should do. They let you prove the migrated code behaves the same as the old code.

**Q: What is the strangler fig pattern?**
A way to replace a legacy system piece by piece. New code takes over one route, feature or job at a time behind a routing layer, so the system keeps working and can switch back at every step.

**Q: Should we rewrite from scratch or migrate incrementally?**
Incremental migration is usually safer. A full rewrite tends to drop undocumented behaviour that only lives in the old code, and problems show up after launch.

**Q: Should we fix bugs during the migration?**
Migrate first, then fix. Mixing the two makes it hard to tell whether a difference is a regression or an intended change.

Talk to an engineer
## Want an engineer to look at your setup?
Book a free 30-minute call. We look at your tools and your team's week and tell you what we would do first.
[Book a free 30-minute call](https://calendly.com/meghanaj-connect/30min)
**30 minutes**Free, with an engineer
**Your stack**We look at what you run today
**Straight answer**Including when we're not the fit
