Skip to content
We found 22 year old bug in COBOL batch job. We kept it.
experience2 min read509 viewsSep 11, 2026

We found 22 year old bug in COBOL batch job. We kept it.

September 11, 2026 · 2 min read

#1 in Legacy Code Archaeology

509 views4 comments

Currently I am migrating nightly batch jobs from COBOL to Java in core banking system. Some jobs are older than some of my colleagues. Everybody wants to "modernise", which usually means somebody wants microservices. We did not do microservices. We did something boring and it saved us.

One job calculates interest accrual for savings accounts. We rewrote it in Java with BigDecimal, unit tests, code review, everything nice. Then we ran old and new job in parallel on same production input every night and compared output files line by line. First night: 1,840 accounts different. Differences of one grosz.

Java was correct. COBOL was truncating intermediate result, because field was declared with two decimal places and nobody wrote ROUNDED in the COMPUTE. So since around 2003 every daily accrual was cut, not rounded. Small amount per account, per day.

Now the fun part. Downstream reconciliation, general ledger postings and two regulatory reports were all built and tested against truncated numbers. Customer statements match truncated numbers. If we "fix" it in migration, migration is suddenly not migration. It is change of interest calculation for whole bank, with legal review, customer communication and probably regulator.

So we kept the bug. In Java we have now method called truncateLikeLegacy with long comment explaining why, and separate ticket for business to decide if they want to change behaviour. That ticket is open eleven months. This is fine.

What I learned (again, I am too old to learn things first time):

  • Migration and behaviour change must be separate projects. Never fix bugs during migration, write them down.
  • Parallel run with output comparison is worth more than all unit tests. Unit tests check what you think code does. Comparison checks what it actually does.
  • Old code is not stupid. It is often wrong in ways the whole company now depends on.

We run parallel comparison for every job, minimum one full month, including month end and one quarter end. Quarter end always finds something.

Written by

Tomasz Kowalski

Java/Kotlin dev, 17 years, mostly banking core systems in Kraków. Currently migrating COBOL batch jobs that are older than me. Sceptical of hype by default.

Get the best of #legacy-code-archaeology weekly

One email each Monday with the most useful posts. No spam; unsubscribe in one click.

Comments

4

Sign in to join the conversation.

Sign in to comment
Arjun Mehta@arjunmehta#2 in Legacy Code Archaeology
Parallel run is the only honest migration strategy for anything touching money. We did same for our loan EMI engine and found a day count convention difference (actual/365 vs 30/360) that would have changed every repayment schedule. truncateLikeLegacy is a beautiful method name.
1 net votes
Hannah Weiss@hannahweiss#2 in Legacy Code Archaeology
Hospitals have the same pattern: a lab system rounds a value one way, and every reference range and alert downstream is calibrated to it. 'Old code is often wrong in ways the whole company depends on' should be printed on every modernisation proposal.
3 net votes
Kevin Tran@kevtran_dev#2 in Legacy Code Archaeology
this is so cool and terrifying. how do you even compare output files that big? like a diff tool or something custom
1 net votes
Tomasz Kowalski@tomasz_k#1 in Legacy Code Archaeology
Custom. Both jobs write records sorted by account number, small program walks both files together and reports differences with key and field name. Plain diff is useless when one field is different on 2 million lines. Is maybe 300 lines of Java. Best 300 lines I wrote this year.
0 net votes