Skip to content
Modernisation14 min readMay 2026

Translating a 2.1M line core without stopping the business

Fourteen months on a mainframe core banking migration. What held up, what we would refuse to do again, and why the test suite ended up mattering more than any model we used.

Gowtham Ragothaman
Head of Engineering, Zitrino
Ask about modernisation

The shape of the thing

Two point one million lines of COBOL, a batch schedule with a hard cut-off at 04:20, and eleven downstream systems that had been consuming the same fixed-width files since the mid-nineties. Two of the original authors were still employed, one of them part-time. The rest of the knowledge lived in the code and in a shared drive of Word documents that disagreed with each other.

The board had asked for a rewrite twice before and cancelled it twice. That history was the most useful information we were given. Both attempts had started by designing the target architecture, and both had run out of political capital before touching the batch chain, because the batch chain is where all the fear lives.

Eleven weeks reading before writing anything

We spent the first quarter of the engagement doing nothing a stakeholder would call progress. Automated extraction of every program, copybook and JCL step into a dependency graph; then model-assisted summarisation of each program into plain-language behaviour notes; then, critically, review of those notes by the two people who still knew the domain.

The summarisation was the cheap part. The review is where the value appeared. Roughly one program in six contained behaviour nobody could explain, and about a third of those turned out to be load-bearing. A rounding rule applied only to accounts opened before a 2003 product migration. A retry loop that silently absorbed a specific downstream failure that still happens twice a year.

One program in six did something nobody could explain. A third of those turned out to matter. You cannot find that ratio by reading an architecture diagram.

The test suite mattered more than the model

The decision that saved the programme was made in week nine: before translating anything, build a behavioural harness from production data. Twelve months of real transaction volumes, replayed through the legacy system in a copy environment, with every output file, journal entry and error record captured as the expected result.

It took seven weeks and it was tedious. It also meant that from then on, correctness was a question with an answer. When translated Java produced a different penny on a leap-year interest calculation, we knew within an hour rather than in a customer complaint eight months later. Every team member who complained about the harness in month three defended it in month ten.

If I could give one instruction to a team starting this work, it would be that the harness is the deliverable of the first phase. The translation is comparatively mechanical once you can prove equivalence.

The order we migrated in

We ran strangler-style, with the legacy system authoritative until each slice had run in parallel for two full month-end cycles. Month-end matters more than daily volume, because month-end is where the unusual code paths live.

Phase 1Read-only enquiry paths. No business risk, real production traffic, and it proved the data access layer under load.
Phase 2Reporting and extract generation. Output is byte-comparable against the legacy files, so divergence is trivially detectable.
Phase 3Transaction posting, one product family at a time, dual-run for two month-ends before the legacy path was disabled.
Phase 4The overnight batch chain, last, when the team had a year of evidence that the translated logic behaved.
Phase 5Decommissioning, which we scoped and budgeted as a project rather than a task, because it never is one.

Three things we would not repeat

We accepted a target architecture handed down before the understanding phase finished. Half of it was fine and half of it was designed against an assumed data model, and unpicking that cost about five weeks. Now we treat the target design as an output of discovery, not an input to it.

We also let translated code be reviewed by whoever was free, rather than by someone who understood the original domain. That produced technically correct Java that quietly renamed concepts, and six months later two teams were using different words for the same balance type. Language drift is a real migration risk and it is invisible until documentation gets written.

And we under-resourced the downstream consumers. Eleven systems depended on those files; we assumed byte-identical output meant no work for them. It did, except for the two that had grown to depend on the legacy system’s timing rather than its content.

Byte-identical output does not mean nothing changed downstream. Two consumers depended on when the file arrived, not what was in it.

Where it landed

Fourteen months, no unplanned customer-facing outage, and one incident worth reporting: a two-hour delay in a non-critical extract during the second dual-run month-end, caused by a scheduling assumption rather than translated logic. Batch window went from three hours forty minutes to fifty-five minutes, which was not the goal but was the number the board remembered.

The honest caveat is that this was one core with unusually disciplined data. We have looked at estates where the same approach would take three years, mostly because the data model has been extended by twenty years of urgent fixes and there is no equivalent of month-end to test against. The method holds; the timeline is a property of your estate, not of the tooling.

What we tell people now

The practical claim

A model can read COBOL better than most of the people left who wrote it. What it cannot do is tell you whether the behaviour it just reproduced was the intended behaviour or a workaround from 1997 that the business now depends on. That question is the project.

Discuss a modernisation