Legacy system modernization with AI comes down to a sequence, not a preference. First: can you characterize what the current system actually does, as opposed to what the documentation claims it does? Second: does the system have seams you can cut along — a real API boundary, separable data, anything other than one shared database that every module writes to directly? Third: can the business tolerate a change freeze while you work?

Answer those three in order and the path picks itself. Refactor in place if the runtime is still supported and you can still hire people who know it. Rewrite fully only if you can characterize the current behaviour and pause the business. Strangle incrementally in every other case — which, in practice, is most cases.

AI changed the first step, dramatically. It did not change the other two at all. Almost every bad modernization decision I’ve watched people make in the last two years comes from assuming it changed all three.

Why the old framing is stale

Rewrite versus refactor is a question from a world where typing was the bottleneck. Both options were priced by how much code a human had to produce; the argument was whether you produced it in one go or gradually.

That assumption is now wrong, in a direction that matters. Producing code in a target language is close to the cheapest step in a modernization project today. What stayed expensive is knowing what the system is supposed to do, and proving the replacement still does it.

The constraint moved. It used to sit on writing. It now sits on establishing equivalence.

Be precise about that word, because it carries most of the weight here. Equivalence does not mean “the new system compiles.” It does not mean “the new system passes the tests we wrote for the new system.” It means: for the inputs the business actually sends, the new system produces the outcomes the old one did — including the outcomes nobody designed and everybody now depends on.

That last clause is where projects die.

What AI genuinely changed

Reading legacy code at scale

This is the real shift, and it gets a fraction of the attention code generation gets.

A three-hundred-thousand-line system in a language nobody on your team writes any more used to be effectively opaque. You could read it the way a person reads a phone book. Building an accurate mental model took months of the one senior engineer you couldn’t spare.

Now you can put a model over the corpus and interrogate it. Which paths write to this table. How many places calculate this fee, and do they agree. The answers need verifying — it’s a starting map, not a survey — but you get them in an afternoon rather than a quarter, and checking a claim about code is far faster than deriving it cold.

Rule extraction is the version with the highest payoff. Point the tooling at the modules encoding business decisions and have it produce a plain-language inventory: every rule, threshold, and exception, with file references so a human can confirm each one.

On one modernization assessment, we extracted the pricing rules from a system whose documentation had been genuinely maintained — someone conscientious had kept it current for years. The extracted rules and the document disagreed in four places. Three were ordinary drift. The fourth was not: the code applied a discount tier that appeared nowhere in the documentation, and that finance, when asked, said should not exist. It had been running for years. Real customers had been receiving it.

Note what the right response was not: “the code is wrong, don’t carry it forward.” Somebody had been quietly getting that discount for years, and what to do about it was a commercial question with a legal edge, not an engineering one. We wrote it up and stopped.

Every system old enough to be called legacy has at least one of these. The only question is whether you find it during extraction, when it’s a memo, or during cutover, when it’s an incident.

Generating characterization tests against the old system

If you take one technique from this article, take this one. It’s the highest-value and least-used move available, and AI made it cheap enough to be routine.

A characterization test doesn’t assert what the system should do. It records what it does — current behaviour, ugly parts included — and fails when that changes. Michael Feathers named the technique two decades ago in Working Effectively with Legacy Code, and it was always the correct answer. Writing enough of them by hand just cost more than most modernization budgets had.

That cost collapsed. You can now generate a broad suite by having tooling enumerate inputs, capture outputs, and pin them — before you touch anything, against the system as it runs today.

Two things follow. Your definition of equivalence becomes executable rather than aspirational, and it arrives in a form your replacement must satisfy under any of the three paths, so it isn’t wasted if the plan changes.

If a modernization proposal doesn’t start here, it’s a proposal to find out at cutover.

Translating idioms between languages and frameworks

The headline capability, and the one I’d rank third. Current tooling will take COBOL, VB6, Delphi, or a decade-old framework and produce compiling, idiomatic output in something modern. That’s real, and it saves a step that used to be slow and demoralizing.

But compiling is not equivalence. A translation can be syntactically perfect and behaviourally wrong in ways that stay invisible until they aren’t: rounding, date and timezone handling, sort order, null semantics, the order in which side effects happen. Financial systems are unforgiving here, because rounding differences don’t crash. They quietly produce numbers that are slightly off, and get found by a customer.

Which is the argument for characterization first. Translation is fast now. Verification is what you’re buying.

What it did not change

Data migration correctness is untouched: getting years of production data — dead fields, encoding oddities, rows that violate constraints you’d now enforce — into a new schema without loss is as hard as it ever was. Integration contracts are untouched, because every system talking to yours has expectations, some undocumented, some belonging to organizations that won’t rewrite anything to accommodate you. Regulatory sign-off is untouched.

So is the political problem. Somebody owns the old system and their standing is bound up in it. A plan that treats them as an obstacle rather than your richest source of knowledge will lose access to the knowledge. I’ve watched that cost more schedule than any technical problem on the same project.

The three paths and their disqualifying conditions

Frameworks that end in “it depends” are useless. Here are the conditions that take each option off the table.

Refactor in place

Keep the system and improve it incrementally: modularize, add tests, upgrade dependencies, pay down debt where it hurts.

Disqualified when the runtime is out of vendor support and no longer getting security patches. Disqualified when you cannot hire for it — honestly, meaning at a rate you’d pay, in your market, for people who’ll stay. If “what happens when the person who knows this leaves” gets silence, refactoring in place is deferral in a technical costume.

Underrated the rest of the time. A supported-but-ugly system a team can still work on is a normal engineering problem, not a modernization programme.

Full rewrite

Build the replacement, cut over, retire the original.

Disqualified when the business cannot pause changes to the old system. That’s the mechanism behind nearly every “two years in and nobody knows when it ends” story, and it isn’t technical. The rewrite targets the system as it existed on day one, the business keeps needing changes, so the target keeps moving and the new team spends its capacity chasing it. Nobody involved is doing anything wrong. The plan was wrong.

Also disqualified when the business rules are undocumented and the people who wrote them are gone — unless you’ve done extraction and characterization first. That work is exactly what converts this disqualifying condition into a manageable one, and it’s the most genuinely new thing about modernization right now.

Strangle incrementally

Route traffic through a layer in front of the old system, replace one slice at a time behind it, retire each slice as it’s replaced. Martin Fowler’s strangler fig framing is the canonical description.

Disqualified when the system has no seams. That’s the practical core of this article, so here’s the test.

Take one candidate slice — a business capability, not a technical layer. Can you put an interception point in front of it, so requests route to either implementation? Can you identify the data it owns, and is that data written by anything else? Can you define “done” as a decommission, code actually deleted, rather than “new path available”?

Three yeses and you have a seam. The usual failing answer is the second, and the usual cause is a shared database every module writes to directly. When six modules write the same table, you can’t move one without moving the invariant those six jointly maintain, and no interception point fixes that.

The fair objection: “strangler pattern is consultant-speak for a project that never finishes.” That lands, and I’ve seen the version that earns it — a proxy layer, three replaced slices, then years of both systems running because the rest were the hard ones.

What makes it terminate is unglamorous. Every slice gets a written completion definition and a decommission date, and completion means the old code is deleted, not bypassed. A slice that’s “done” while its original still runs is just a second system you now maintain. Track one number in front of leadership — how much of the old system is gone — never how many new services got built.

The decision sequence

In order, because each step changes what the later ones are worth.

  1. Can you establish equivalence? Can you characterize current behaviour well enough to prove a replacement matches it? If not, that’s the first project whatever you choose. It’s weeks now, not quarters.
  2. Does it have seams? Run the three-question test on your two or three most valuable capabilities. If none passes, incremental replacement isn’t available yet, and creating one seam is the real first slice.
  3. Can the business freeze changes? Get a real answer from the people who’d live with it, not an aspirational one from a steering committee.
  4. Then choose. Supported runtime and hireable skills: refactor. Equivalence established and a genuine freeze available: rewrite is on the table. Seams but no freeze: strangle. Neither: build a seam first, and treat that as the project.

Government and regulated variants

Everything above holds, with three additions that change the timeline more than the approach.

Sign-off cycles are long and don’t parallelize by adding engineers. Data residency may constrain not just where the new system runs but where migration work happens and which tooling may touch the code — resolve that first, because it can eliminate options you’ve already priced.

The audit requirement is the underestimated one. In a regulated setting you often need past decisions to be reconstructible, so the replacement has to explain outcomes the old system produced years ago. That quietly makes rule extraction a compliance deliverable rather than an engineering convenience.

None of it changes the sequence. It changes how much of the calendar belongs to engineering rather than to waiting.

A realistic first 30 days

Not a plan for the programme. A plan for finding out whether the programme is viable.

Week 1 — extraction. Run rule extraction across the modules encoding business decisions. Produce a plain-language inventory with file references, have a human verify a sample, and log every place the rules contradict the documentation. Those contradictions are findings for the business, not just engineering.

Week 2 — characterization. Generate a suite against the current system and get it running green in CI against the old code. Equivalence is now executable, and useful under every path.

Week 3 — seams. Map candidate slices, run the three-question test on each, rank by business value over seam difficulty. Name the shared-database entanglements explicitly; they decide whether incremental replacement is available at all.

Week 4 — one thin slice. Pick the smallest slice that passes the seam test and has real users. Interception point in, slice replaced, original code deleted. Then measure how long it took, honestly, including the arguments.

That last number is the only credible input to your programme estimate. Everything before it is planning; the thin slice is data. If week 4 goes badly you’ve learned it for the price of a month instead of a year. If you want an outside read on whether your system has seams before committing budget to the answer, that’s the assessment worth buying — a month, not a discovery phase.

Where I disagree with the common advice

The orthodoxy here descends from Joel Spolsky’s argument, written back in 2000, that rewriting from scratch is the worst strategic mistake a software company can make. The insight was that old code is ugly because it accumulated fixes for real problems, and throwing it away throws away the knowledge those fixes represent.

That was right — because reading legacy code was expensive and writing it was the bottleneck, so the knowledge was effectively unrecoverable and you had to keep the artifact containing it.

Both halves have changed, and for small-to-mid systems the conclusion has shifted. When you can extract that knowledge into a reviewed inventory and pin current behaviour into an executable suite, you’re no longer discarding it along with the artifact. You’re separating two things that used to be inseparable, and the never-rewrite reflex is now costing some teams a decision they could safely make.

Where the orthodoxy still holds exactly: undocumented behaviour carrying regulatory consequence. Extraction gives a probable reading of what the code does, and probable plus human verification is fine for most systems. Where a wrong edge case means a reportable compliance failure, probable isn’t the standard, verifying every rule costs more than the rewrite saves, and the old system’s chief virtue is that it’s already the accepted answer.

So: ordinary business rules, and being wrong means an angry customer and a fix — the rewrite option is more open to you than received wisdom suggests, provided extraction and characterization come first. If being wrong means a regulator, stay incremental. The line isn’t system size. It’s the cost of a wrong edge case.

Frequently asked questions

Can AI automatically convert legacy code to a modern language?

It produces compiling, idiomatic output in a target language, and that part genuinely works. But compiling is not equivalence. Translations go wrong on rounding, dates, timezones, sort order, null semantics, and side-effect ordering — failures that don’t crash, they just produce quietly wrong results. Treat translation as a fast first draft a characterization suite has to validate, not a finished migration.

Is it better to rewrite or refactor a legacy system?

Neither by default. Refactor if the runtime is supported and you can still hire for it. Rewrite only if you can characterize current behaviour and the business can genuinely pause changes to the old system. If you can’t freeze, strangle instead — a rewrite against a moving target is the most common way these projects fail.

How do you modernize a system when the business rules aren’t documented?

Extract them before deciding anything. Run AI-assisted rule extraction across the modules encoding business decisions, produce a plain-language inventory with file references, and have a human verify it. Expect contradictions with whatever documentation exists; those are findings the business needs. This step used to take a quarter and now takes about a week.

What is the strangler pattern and when does it not work?

You put an interception layer in front of the old system, replace one capability at a time behind it, and delete each replaced slice. It fails when the system has no seams — typically a shared database every module writes to directly, so nothing moves independently. It also fails when slices are called done while the original still runs, which is how it earned its never-finishes reputation.

How long does a legacy modernization project take?

Anyone answering without seeing your system is guessing. What you can do in about a month is find out: extract the rules, build a characterization suite, map the seams, and fully replace one thin slice including deleting the old code. How long that slice takes is the only honest input to estimating the rest — far cheaper to learn in week four than year two.

Receive A Complimentary Consultation

Book Now