{
"$type": "site.standard.document",
"bskyPostRef": {
"cid": "bafyreie4lddckac36dhdqboou47qakt5tuontpgumnkxxwqbexfspimegq",
"uri": "at://did:plc:25rdn5elo5izoxrmtis34zuk/app.bsky.feed.post/3mogxqaqq3pr2"
},
"coverImage": {
"$type": "blob",
"ref": {
"$link": "bafkreicqsjdhicofj2scdx3ufru7h5bzax6hky533eet7oxwbpuogz2jge"
},
"mimeType": "image/webp",
"size": 85132
},
"path": "/banuap/zero-variance-proving-cobol-to-java-semantic-equivalence-with-a-live-mainframe-emulator-on-aws-1ani",
"publishedAt": "2026-06-16T23:03:18.000Z",
"site": "https://dev.to",
"tags": [
"mainframe",
"programming",
"modernization",
"https://github.com/SDL-Hercules-390/hyperion",
"https://spring.io/projects/spring-batch",
"https://aws.amazon.com/sdk-for-java/",
"https://registry.terraform.io/providers/hashicorp/aws/latest",
"@Primary",
"@Configuration",
"@Bean",
"@ConfigurationProperties",
"@Component",
"@Override",
"@ArchTest",
"@ConditionalOnProperty"
],
"textContent": "**By Banu Parasuraman**\n\n_Distinguished Engineer | Account CTO | Mphasis_\n\n> _\"The mainframe isn't going away. But the teams who understand it are. The window to modernize safely — before institutional knowledge walks out the door — is closing.\"_\n\n## Introduction\n\nMainframe modernization is one of the most consequential — and most feared — programs in enterprise IT. Billions of dollars of financial transactions flow daily through COBOL batch jobs that have been running, largely unchanged, for three to four decades. The fear is rational: these systems work. The risk of breaking them during migration is real, and the consequences in financial services are severe.\n\nThe standard industry answer to this risk is the **parallel run** : run the old system and the new system simultaneously, compare their outputs, and only cut over when you can prove the new system produces identical results. In theory, elegant. In practice, parallel runs are notoriously difficult to instrument, especially when the legacy system is a mainframe batch job with VSAM files, JES2 job scheduling, and OS/VS COBOL business logic that predates most of its current operators.\n\nThis article documents a working parallel run proof-of-concept I built to address exactly this challenge. The goal was to answer a single engineering question with evidence, not architecture diagrams:\n\n**Can we prove, at the record level, that a Java Spring Batch job produces identical NAV calculations to an OS/VS COBOL batch job running on a real S/370 mainframe — before we touch a single line of production code?**\n\nThe answer is yes. And it runs on AWS EC2.\n\n## The Technical Challenge\n\nThe legacy system in scope is a COBOL batch orchestration layer that drives a commercial financial platform. The batch processes position data — fund holdings, security quantities, and net asset values — through a series of computational waves, writing intermediate state to VSAM KSDS datasets and committing final results to an Oracle database.\n\nThe modernization target is Java 17 with Spring Batch 5.x, reading from the same Oracle input, computing the same NAV calculations, and writing to a parallel Oracle schema. The reconciliation question is simple: does `COBOL_NAV == JAVA_NAV` for every position, for every fund, for every batch run?\n\nTo answer that question credibly, you need a real mainframe running real COBOL, not a simulator or a translation layer. That's where SDL Hercules comes in.\n\n## The Architecture\n\nThe POC runs on three AWS components connected by a private VPC:\n\n\n\n ┌─────────────────────────────────────┐\n │ LEGACY LANE (EC2 m5.xlarge) │\n │ │\n │ SDL Hyperion 4.9.1 │\n │ MVS 3.8j / JES2 │\n │ OS/VS COBOL (IKFCBL00) │\n │ VSAM KSDS position store │\n │ Card reader socket :3505 │\n └──────────────────┬──────────────────┘\n │ Oracle write\n ▼\n ┌─────────────────────────────────────┐ ┌─────────────────────────┐\n │ MODERNIZATION LANE (EC2 m5.large) │ │ Amazon RDS 19c │\n │ │ │ │\n │ Java 17 / Spring Boot 3.2 │────►│ cobol_pos.positions │\n │ Spring Batch 5.x │ │ java_pos.positions │\n │ NavCalculationProcessor │ │ recon.parallel_run_diff│\n │ OraclePositionWriter │ │ admin.position_seed │\n └─────────────────────────────────────┘ └─────────────────────────┘\n\n\n**The key design principle:** Java never touches VSAM. VSAM is internal to the COBOL batch — it is the scratchpad between waves. Java reads from the same Oracle input that COBOL reads, computes NAV independently, and writes to a separate Oracle schema. The reconciliation view diffs the two schemas. When variance is zero across a full batch window, the cutover flag flips.\n\nThis is architecturally honest. In production, COBOL writes to VSAM as intermediate working storage and commits final results to Oracle. Java replaces that entire pipeline — VSAM disappears when the last COBOL job retires, replaced by Spring Batch's in-memory step context and database row locking.\n\n## Building the Mainframe Emulator Layer\n\n### Why SDL Hyperion, Not Docker\n\nThe first architectural decision was how to run the mainframe emulator. Docker images exist for Hercules + MVS TK4-, and they work. But for this use case — demonstrating parallel run credibility to senior engineering stakeholders — a bare-metal build is more defensible. It shows the emulator is running the actual hardware instruction set, not a pre-packaged black box.\n\nSDL Hyperion 4.9.1 was built from source on Ubuntu 22.04 with the SoftFloat-3a external package for IEEE 754 floating point emulation. The build sequence:\n\n\n\n # Clone SDL Hyperion\n git clone https://github.com/SDL-Hercules-390/hyperion.git\n cd hercules\n\n # Build external packages (SoftFloat, crypto, telnet)\n # Critical: use absolute paths in extpkgs.sh.ini — tilde ~ does not expand\n ./extpkgs.sh c d s t\n\n # Configure and build\n ./configure --enable-extpkgs=/home/ubuntu/mainframe/extpkgs/install\n make -j$(nproc)\n sudo make install\n sudo ldconfig\n\n\nBuild time on an m5.xlarge is approximately 20 minutes. The result is Hercules 4.9.1 with full z/Architecture support, CCKD DASD emulation, and TN3270 terminal support.\n\n### MVS 3.8j TK4- as the Guest OS\n\nMVS 3.8j TK4- Update 08 is the de facto standard free MVS distribution, maintained by the Tur(n)key project. It includes JES2, RACF security (via RAKF), TSO/ISPF, the OS/VS COBOL compiler (IKFCBL00), IDCAMS, and a full set of system utilities.\n\nThe primary mirror (ETH Zurich) was unavailable at time of build; the distribution was cloned from a GitHub mirror. The bundled Hercules binary was removed and replaced with our SDL 4.9.1 build.\n\n### Auto-IPL as a Systemd Service\n\nFor a POC that needs to survive EC2 reboots, the emulator must start automatically. The solution is a systemd service with an auto-IPL script:\n\n\n\n # /etc/systemd/system/hercules-mvs.service\n [Service]\n User=ubuntu\n WorkingDirectory=/home/ubuntu/mainframe/tk4/tk4-_v1.00_current\n Environment=MODE=CONSOLE\n Environment=RDRPORT=3505\n ExecStart=/usr/local/bin/hercules -f conf/tk4-.cnf -r scripts/auto_ipl.rc\n\n\nThe `auto_ipl.rc` script pauses for device initialization, IPLs from volume 148 (`ipl 148`), and responds to the MVS system parameter prompt (`/`). MVS boots in approximately 20 seconds post-EC2-start, JES2 initializes, and the card reader socket on port 3505 begins accepting JCL.\n\n**Lesson learned:** MVS 3.8j's IPL device address is 148, not 150 as some documentation suggests. The COBUCG procedure lives in SYS2.PROCLIB (on PUB000), not SYS1.PROCLIB (on MVSRES) — a detail that matters when patching compiler parameters.\n\n## The COBOL Batch Program\n\n### OS/VS COBOL Dialect Constraints\n\nOS/VS COBOL (compiler IKFCBL00) predates COBOL 85 by several years. Several features that modern COBOL programmers take for granted are not available:\n\nFeature | Status in IKFCBL00\n---|---\n`ORGANIZATION IS INDEXED` (VSAM) | Not supported — causes S0C4\n`PERFORM ... END-PERFORM` inline | Not supported — use paragraph PERFORM\nSubstring reference modification `(1:2)` | Not supported — use REDEFINES\n`ASSIGN TO ddname` for sequential | Must use `UT-S-ddname` format\n\nThe VSAM limitation is the most significant. The standard expectation — that COBOL writes directly to a VSAM KSDS using `ORGANIZATION IS INDEXED` — does not work in this compiler version. The correct pattern for this dialect is the **two-step approach** :\n\n\n\n COBOL writes fixed-length records to a sequential temp dataset (&&POS)\n IDCAMS REPRO loads &&POS into the VSAM KSDS\n\n\nThis is not a POC workaround — it is architecturally accurate. In production mainframe shops, COBOL batch jobs routinely write to intermediate sequential datasets that are then processed by IDCAMS or sort utilities. The two-step pattern reflects real-world practice.\n\n### The COBUCG Proc BUF Parameter\n\nThe most time-consuming debugging issue was the `IKF0015I-C BUF PARM TOO SMALL FOR DD-CARD BLKSIZES` compiler abandonment error. The root cause: the COBUCG procedure in SYS2.PROCLIB had a `BUF=1024K` parameter that was too small for the SYSUT work file block sizes, and the SYSUT DDs used `SPACE=(460,...)` — a value the compiler interprets as blocksize.\n\nThe fix required three changes to the COBUCG proc via IEBUPDTE:\n\n 1. Increase `BUF=1024K` to `BUF=4096K`\n 2. Change SYSUT SPACE from `(460,...)` to `(800,...)`\n 3. Change SYSLIN SPACE from `(80,...)` to `(800,...)`\n\n\n\nAnd specify `BUF=4096` in the `PARM.COB` override in the calling JCL, since `PARM.COB` appends to the proc's PARM rather than replacing it.\n\n### The Final JCL Pattern\n\n\n //IVCOBOL JOB (ACCT),'POC JOB',CLASS=A,MSGCLASS=A,\n // USER=HERC01,PASSWORD=CUL8TR\n //STEP1 EXEC COBUCG,PARM.COB='SUPMAP,NOSEQ,NOTRUNC,BUF=4096'\n //COB.SYSLIB DD DSN=SYS1.COBLIB,DISP=SHR\n //COB.SYSIN DD *\n ... COBOL source ...\n SELECT POS-FILE ASSIGN TO UT-S-POSOUT\n ORGANIZATION IS SEQUENTIAL.\n ...\n WRITE POS-RECORD\n /*\n //GO.POSOUT DD DSN=&&POS,DISP=(NEW,PASS),UNIT=SYSDA,\n // SPACE=(TRK,(5,2)),DCB=(RECFM=FB,LRECL=85,BLKSIZE=850)\n //GO.SYSOUT DD SYSOUT=A\n //STEP2 EXEC PGM=IDCAMS,COND=(0,NE)\n //SYSPRINT DD SYSOUT=A\n //SEQIN DD DSN=&&POS,DISP=(OLD,DELETE)\n //VSAMOUT DD DSN=HERC01.IVPOS,DISP=SHR\n //SYSIN DD *\n REPRO INFILE(SEQIN) OUTFILE(VSAMOUT)\n /*\n //STEP3 EXEC PGM=IDCAMS,COND=(0,NE)\n //SYSPRINT DD SYSOUT=A\n //SYSIN DD *\n LISTCAT ENTRIES(HERC01.IVPOS) ALL\n /*\n\n\n**Final job result:**\n\n\n\n STEP1 COB IKFCBL00 RC= 0000\n STEP1 GO LOADER RC= 0000\n STEP2 IDCAMS RC= 0000\n STEP3 IDCAMS RC= 0000\n IDC0005I NUMBER OF RECORDS PROCESSED WAS 6\n\n\n### VSAM KSDS Configuration\n\nThe VSAM dataset was defined with the `UNIQUE` parameter — critical in MVS 3.8j when VSAM suballocation pools are exhausted or uncatalogued:\n\n\n\n DEFINE CLUSTER (NAME(HERC01.IVPOS)\n INDEXED\n KEYS(8 0)\n RECORDSIZE(85 85)\n VOLUMES(PUB000)\n CYLINDERS(1 1)\n UNIQUE)\n DATA (NAME(HERC01.IVPOS.DATA))\n INDEX (NAME(HERC01.IVPOS.INDEX))\n CATALOG(SYS1.UCAT.TSO)\n\n\n`UNIQUE` allocates space directly on the DASD volume without requiring a pre-existing VSAM space entry — bypassing the `IDC3025I INSUFFICIENT SUBALLOCATION DATA SPACE` error that occurs when the catalog's freespace records have been consumed by previous delete/redefine cycles.\n\n## The Java Spring Batch Layer\n\n### Dual Datasource Configuration\n\nSpring Boot's datasource autoconfiguration assumes a single primary datasource. With both PostgreSQL (for Spring Batch metadata) and Oracle (for business data), the configuration requires explicit `@Primary` annotation:\n\n\n\n @Configuration\n public class PrimaryDataSourceConfig {\n @Bean @Primary\n @ConfigurationProperties(prefix = \"spring.datasource\")\n public DataSourceProperties primaryDataSourceProperties() {\n return new DataSourceProperties();\n }\n\n @Bean @Primary\n public DataSource dataSource() {\n return primaryDataSourceProperties()\n .initializeDataSourceBuilder().build();\n }\n }\n\n @Configuration\n public class OracleDataSourceConfig {\n @Bean\n @ConfigurationProperties(prefix = \"oracle.datasource\")\n public DataSourceProperties oracleDataSourceProperties() {\n return new DataSourceProperties();\n }\n\n @Bean(name = \"oracleDataSource\")\n public DataSource oracleDataSource() {\n return oracleDataSourceProperties()\n .initializeDataSourceBuilder().build();\n }\n }\n\n\nWithout `@Primary` on the PostgreSQL datasource, Spring Batch's schema initializer picks up the Oracle datasource and fails with `Failed to determine DatabaseDriver`.\n\n### The NAV Calculation Processor\n\nThe core calculation mirrors COBOL's `COMPUTE MARKET-VALUE = QUANTITY * NAV`:\n\n\n\n @Component\n public class NavCalculationProcessor implements ItemProcessor<Position, Position> {\n @Override\n public Position process(Position position) throws Exception {\n BigDecimal marketValue = position.getQuantity()\n .multiply(position.getNav())\n .setScale(2, RoundingMode.HALF_UP);\n position.setMarketValue(marketValue);\n position.setSource(\"JAVA\");\n return position;\n }\n }\n\n\nThe use of `BigDecimal` with explicit `RoundingMode.HALF_UP` is deliberate. COBOL's packed decimal arithmetic rounds at half-up by default. Using `double` or `float` in Java introduces floating-point precision errors that will produce spurious mismatches in the reconciliation view — a subtle but critical correctness issue.\n\n### Composite Writer — Dual Output\n\nThe Spring Batch step writes to both Oracle (`java_pos.positions`) and PostgreSQL simultaneously using a `CompositeItemWriter`:\n\n\n\n @Bean\n public Step positionStep(JobRepository jobRepository,\n PlatformTransactionManager transactionManager) {\n CompositeItemWriter<Position> writer = new CompositeItemWriter<>();\n writer.setDelegates(List.of(oracleWriter, rdsWriter));\n\n return new StepBuilder(\"positionStep\", jobRepository)\n .<Position, Position>chunk(10, transactionManager)\n .reader(oracleItemReader) // reads admin.position_seed\n .processor(processor) // computes QTY × NAV\n .writer(writer) // writes to Oracle + PostgreSQL\n .build();\n }\n\n\nOracle receives the primary business output. PostgreSQL stores a copy for audit and for the reconciliation dashboard.\n\n## The Oracle Reconciliation Schema\n\nThe parallel run schema separates COBOL and Java outputs into distinct schemas on the same Oracle RDS instance:\n\n\n\n -- COBOL lane\n CREATE TABLE cobol_pos.positions (\n fund_id VARCHAR2(10),\n security_id VARCHAR2(20),\n quantity NUMBER(18,6),\n nav NUMBER(18,6),\n market_value NUMBER(18,2),\n as_of_date DATE,\n source VARCHAR2(10) DEFAULT 'COBOL',\n CONSTRAINT pk_cobol_pos PRIMARY KEY (fund_id, security_id, as_of_date)\n );\n\n -- Java lane\n CREATE TABLE java_pos.positions (\n ... identical structure, source DEFAULT 'JAVA' ...\n );\n\n -- Reconciliation view\n CREATE OR REPLACE VIEW recon.parallel_run_diff AS\n SELECT\n c.fund_id,\n c.security_id,\n c.as_of_date,\n c.market_value AS cobol_mv,\n j.market_value AS java_mv,\n ABS(c.market_value - j.market_value) AS variance,\n CASE WHEN ABS(c.market_value - j.market_value) < 0.01\n THEN 'MATCH' ELSE 'MISMATCH' END AS status\n FROM cobol_pos.positions c\n JOIN java_pos.positions j\n ON c.fund_id = j.fund_id\n AND c.security_id = j.security_id\n AND c.as_of_date = j.as_of_date;\n\n\nThe reconciliation view is the sign-off artifact. When this view returns zero rows with `status = 'MISMATCH'` across a full batch window, the cutover decision can be made with engineering confidence.\n\n## The Result\n\n\n SELECT fund_id, security_id, cobol_mv, java_mv, variance, status\n FROM recon.parallel_run_diff\n ORDER BY fund_id, security_id;\n\n\n\n FUND_ID SECURITY_ID COBOL_MV JAVA_MV VARIANCE STATUS\n --------- ------------- ---------- ---------- ---------- --------\n FUND01 AAPL 146625 146625 0 MATCH\n FUND01 IBM 185250 185250 0 MATCH\n FUND01 MSFT 210050 210050 0 MATCH\n FUND02 BAC 63450 63450 0 MATCH\n FUND02 GS 93000 93000 0 MATCH\n FUND02 JPM 64725 64725 0 MATCH\n\n 6 rows selected — VARIANCE = 0 — STATUS = MATCH\n\n\n**Zero variance. Six positions. COBOL and Java produce identical results.**\n\n## Key Engineering Lessons\n\n### 1. VSAM Is a Scratchpad, Not a Database\n\nThe most important architectural clarification for modernization teams: VSAM is the batch processing scratchpad, not the system of record. COBOL reads VSAM during a batch run and discards it afterward. Java does not need to read VSAM — it needs to produce the same Oracle output that COBOL produces after it has processed VSAM.\n\nThe parallel run proves semantic equivalence at the Oracle layer. VSAM disappears when the last COBOL job retires, replaced by Spring Batch's `StepExecutionContext` for intra-step state and database row locking for the concurrency control that VSAM lock files previously provided.\n\n### 2. BigDecimal Is Non-Negotiable in Financial Calculations\n\nEvery financial calculation in the Java lane uses `BigDecimal` with explicit `RoundingMode`. COBOL's packed decimal arithmetic is deterministic and rounds at half-up. Floating-point types in Java are not — they will produce false mismatches in the reconciliation view, eroding confidence in the comparison.\n\nAn ArchUnit rule enforcing this constraint across the codebase is a practical investment:\n\n\n\n @ArchTest\n static final ArchRule noDoubleInFinancialCalculations =\n noFields()\n .that().areDeclaredInClassesThat()\n .resideInAPackage(\"..processor..\")\n .should().haveRawType(double.class)\n .orShould().haveRawType(float.class);\n\n\n### 3. The Cutover Gate Is a Feature Flag, Not a Flag Day\n\nThe parallel run pattern is most powerful when cutover is decoupled from deployment. An AWS AppConfig property (`modernization.cutover.enabled`) controls which lane is the system of record. When the reconciliation view shows zero variance across a defined number of consecutive batch windows, the flag flips — no weekend outage, no code deployment, no rollback plan.\n\n\n\n @ConditionalOnProperty(name = \"modernization.cutover.enabled\",\n havingValue = \"true\")\n @Component\n public class JavaPositionWriter implements ItemWriter<Position> { ... }\n\n @ConditionalOnProperty(name = \"modernization.cutover.enabled\",\n havingValue = \"false\", matchIfMissing = true)\n @Component\n public class CobolBridgeWriter implements ItemWriter<Position> { ... }\n\n\n### 4. Mainframe Emulators Are Underused in Modernization Programs\n\nSDL Hercules running MVS 3.8j is not a toy. It executes real S/370 machine instructions, runs JES2 batch scheduling, supports real VSAM I/O, and compiles real OS/VS COBOL. For modernization programs where production mainframe access is restricted, a Hercules-based POC environment provides a credible, observable, reproducible platform for proving migration approaches before production engagement.\n\nThe cost: one m5.xlarge EC2 instance at approximately $0.19/hour. The credibility: significant.\n\n### 5. JCL Is Unforgiving — Automate Its Generation\n\nJCL has a 80-column fixed-format structure, specific column rules for continuation lines, and character encoding requirements (EBCDIC on the mainframe, but our card reader accepts ASCII with the `ascii` and `trunc` parameters). Manual JCL authoring on a modern keyboard produces subtle errors. All JCL in this POC was generated programmatically using Python's `ljust(80)` padding, then submitted via `nc -w 3 localhost 3505 < job.jcl`.\n\n\n\n lines = [\n \"//MYJOB JOB (ACCT),'DESC',CLASS=A,MSGCLASS=A\",\n \"//STEP1 EXEC PGM=IEFBR14\",\n \"//\"\n ]\n with open('/tmp/job.jcl', 'w') as f:\n for line in lines:\n f.write(line.ljust(80) + '\\n')\n\n\n## Infrastructure as Code\n\nThe entire environment is provisioned via Terraform. One critical lesson: security groups with cross-references must use separate `aws_security_group_rule` resources, not inline `security_groups` references. The latter creates a circular dependency that Terraform cannot resolve:\n\n\n\n # WRONG — causes Terraform cycle error\n resource \"aws_security_group\" \"sg_a\" {\n ingress {\n security_groups = [aws_security_group.sg_b.id] # circular!\n }\n }\n\n # CORRECT — separate resource breaks the cycle\n resource \"aws_security_group_rule\" \"a_allows_b\" {\n type = \"ingress\"\n security_group_id = aws_security_group.sg_a.id\n source_security_group_id = aws_security_group.sg_b.id\n from_port = 3505 to_port = 3505 protocol = \"tcp\"\n }\n\n\n## What This Enables\n\nThis POC framework generalizes beyond NAV calculation. Any COBOL batch program that reads input data and writes to a database can be placed in this parallel run harness:\n\n 1. **Define the input contract** — what Oracle tables or files does the COBOL job read?\n 2. **Define the output contract** — what Oracle tables does it write?\n 3. **Implement the Java equivalent** — Spring Batch reads the same input, computes the same output\n 4. **Wire the reconciliation view** — diff COBOL output vs Java output\n 5. **Set the cutover threshold** — zero variance for N consecutive runs\n\n\n\nThe wave sequencer step — modeling InvestOne's multi-wave batch processing as a Spring Batch `FlowJobBuilder` — is the next engineering milestone. Once wave sequencing is proven, the entire COBOL orchestration layer can be retired wave by wave, with each wave's cutover gated by its own reconciliation view.\n\n## Conclusion\n\nMainframe modernization does not require a big-bang migration or a heroic cutover weekend. It requires a systematic framework for proving equivalence — record by record, calculation by calculation — before any production system is touched.\n\nSDL Hercules on EC2, combined with Spring Batch and Oracle RDS, provides exactly that framework. The COBOL runs on a real S/370 emulator. The VSAM stores real position data. The Java produces real NAV calculations. The Oracle reconciliation view shows the difference between them.\n\nIn this POC, that difference is zero.\n\nThe parallel run is not just a testing pattern. It is a risk management instrument — one that converts the binary choice between \"migrate\" and \"don't migrate\" into a graduated, observable, reversible process. When the variance is zero and the cutover flag flips, it is not a leap of faith. It is a confirmation of what the data has already proven.\n\n## References and Resources\n\n * **SDL Hercules 4.x (Hyperion):** https://github.com/SDL-Hercules-390/hyperion\n * **MVS 3.8j TK4-:** Available via GitHub mirrors of the original Tur(n)key distribution\n * **Spring Batch Reference:** https://spring.io/projects/spring-batch\n * **IBM OS/VS COBOL Programmer's Guide:** IBM GC26-3857 (available via IBM documentation archive)\n * **IDCAMS Reference:** IBM SC26-4562 (Access Method Services for VSAM)\n * **AWS SDK for Java:** https://aws.amazon.com/sdk-for-java/\n * **Terraform AWS Provider:** https://registry.terraform.io/providers/hashicorp/aws/latest\n\n\n\n## About the Author\n\n**Banu Parasuraman** is a Distinguished Engineer and Account CTO at Mphasis, specializing in Forward Deployed Engineering across financial services modernization programs. With over 20 years of experience spanning IBM, Wipro Digital, and General Motors, Banu works embedded in client accounts across application modernization, agentic AI, and enterprise platform initiatives.\n\n_Connect on LinkedIn | Follow for more on mainframe modernization, agentic SDLC, and enterprise platform engineering._\n\n_© 2026 Banu Parasuraman. Published under Creative Commons Attribution 4.0. Technical details have been generalized to protect client confidentiality._",
"title": "Zero Variance: Proving COBOL-to-Java Semantic Equivalence with a Live Mainframe Emulator on AWS"
}