24 min read

The world runs on COBOL. Despite decades of predictions about its demise, COBOL remains the backbone of global commerce, government services, and financial infrastructure. An estimated 220 billion lines of COBOL code are in active production today...

Chapter 39: Legacy System Maintenance and Modernization

Part VIII - Modern COBOL and System Evolution

The world runs on COBOL. Despite decades of predictions about its demise, COBOL remains the backbone of global commerce, government services, and financial infrastructure. An estimated 220 billion lines of COBOL code are in active production today, processing over 95% of ATM transactions, 80% of in-person transactions, and the majority of batch processing in banking, insurance, and government agencies. This chapter addresses the practical realities of maintaining, understanding, and modernizing these massive legacy systems while keeping mission-critical operations running without interruption.

Legacy system modernization is not a theoretical exercise. It is a discipline practiced daily by thousands of programmers who must balance the need for technological advancement against the risk of disrupting systems that process trillions of dollars in transactions every year. The techniques presented here are drawn from real-world modernization projects spanning decades of accumulated experience.


39.1 Understanding Legacy COBOL Systems

What Makes a System "Legacy"

The term "legacy" carries no inherent negative connotation. A legacy system is simply one that has been in production long enough to have accumulated significant business value, institutional knowledge, and technical debt. Most legacy COBOL systems share several defining characteristics.

Scale and complexity. A typical mainframe COBOL application at a large bank or insurance company comprises between 5 and 50 million lines of code spread across thousands of programs. The largest installations exceed 100 million lines. These systems were built over decades by hundreds or thousands of programmers, many of whom have long since retired.

Deep business logic embedding. Legacy COBOL programs encode business rules that have been refined through years of regulatory changes, market shifts, and customer requirements. These rules are often undocumented outside the code itself, making the source code the authoritative specification of how the business operates.

Tight coupling to infrastructure. Legacy programs are deeply intertwined with their execution environment: CICS for transaction processing, IMS or DB2 for data management, JCL for batch scheduling, VSAM for file access. Modernization cannot address the COBOL code in isolation.

Data format dependencies. Legacy systems use EBCDIC character encoding, packed decimal arithmetic (COMP-3), binary representations (COMP), and fixed-length record formats that differ fundamentally from modern computing conventions.

Batch-oriented architecture. Many legacy systems were designed around nightly batch processing cycles. Daily transactions are accumulated and processed in sequential batch runs that update master files. This architecture influences every aspect of the system design.

The Scale of Legacy COBOL

To appreciate the challenge, consider the following statistics that frame the scope of legacy COBOL in production:

  • The U.S. federal government maintains approximately 10 billion lines of COBOL across agencies including the IRS, Social Security Administration, and Department of Veterans Affairs.
  • Major banks typically maintain between 20 and 60 million lines of COBOL for core banking operations.
  • Insurance companies routinely have 10 to 30 million lines of COBOL for policy administration, claims processing, and actuarial calculations.
  • Global COBOL transaction volume exceeds 30 billion transactions per day.

These numbers explain why wholesale replacement is rarely feasible. The risk of rewriting systems that handle this volume of critical transactions is enormous, and the business knowledge embedded in the code cannot be extracted through any automated process with complete fidelity.

Common Legacy System Architectures

Legacy COBOL systems generally follow one of several architectural patterns:

Batch sequential processing. Input files are sorted and processed sequentially against master files, producing updated master files and reports. This is the oldest and most common pattern.

CICS online transaction processing. Users interact through 3270 terminal screens (BMS maps) that invoke COBOL programs through CICS. Each transaction is a short-lived program execution that reads and updates shared resources.

IMS-based hierarchical systems. Programs access data through IMS DL/I calls, navigating hierarchical database structures. The programming model is fundamentally different from relational database access.

Mixed batch and online. Most production systems combine online transaction processing during business hours with batch processing during overnight windows.


39.2 Reading and Understanding Unfamiliar COBOL Code

The first skill required for legacy maintenance is the ability to read and comprehend COBOL programs written by other programmers, often decades ago, frequently without documentation. This is a systematic discipline, not an innate talent.

Initial Assessment

When presented with an unfamiliar program, begin with a structural survey before reading any detailed logic.

      *================================================================*
      * PROGRAM: CLMPROC1                                              *
      * AUTHOR:  J.HENDERSON                                           *
      * DATE:    03/15/1987                                             *
      * PURPOSE: PROCESS INSURANCE CLAIMS - INITIAL VALIDATION         *
      * MODIFICATIONS:                                                  *
      *   04/22/1988 - R.SMITH - ADDED FLOOD COVERAGE CHECK            *
      *   11/03/1990 - T.JONES - Y2K DATE FIX                          *
      *   06/15/1995 - M.WONG  - NEW STATE REGULATIONS                 *
      *   01/10/2000 - K.PATEL - EURO CURRENCY SUPPORT                 *
      *   09/30/2008 - ???     - EMERGENCY FIX PROD ABEND              *
      *   03/22/2015 - D.LEE   - ACA COMPLIANCE CHANGES                *
      *================================================================*
       IDENTIFICATION DIVISION.
       PROGRAM-ID. CLMPROC1.

       ENVIRONMENT DIVISION.
       CONFIGURATION SECTION.
       INPUT-OUTPUT SECTION.
       FILE-CONTROL.
           SELECT CLAIM-INPUT    ASSIGN TO CLMSIN
               FILE STATUS IS WS-CLAIM-STATUS.
           SELECT POLICY-MASTER  ASSIGN TO POLMSTR
               FILE STATUS IS WS-POLICY-STATUS.
           SELECT ERROR-REPORT   ASSIGN TO ERRRPT
               FILE STATUS IS WS-ERROR-STATUS.
           SELECT VALID-CLAIMS   ASSIGN TO VLDCLMS
               FILE STATUS IS WS-VALID-STATUS.

Note what the header tells us: the program has been modified by at least six different people over 28 years. One modification (2008) has an unknown author and was an emergency fix. This modification history is itself valuable intelligence about the program's evolution and potential weak points.

Dead Code Detection

Legacy programs frequently contain dead code, which consists of paragraphs, sections, or entire data definitions that are never referenced. Dead code obscures the actual logic and must be identified before meaningful analysis can proceed.

       WORKING-STORAGE SECTION.
      *--- These fields are actively used ---
       01  WS-CLAIM-RECORD.
           05  WS-CLAIM-NUMBER        PIC X(10).
           05  WS-POLICY-NUMBER       PIC X(12).
           05  WS-CLAIM-DATE          PIC 9(8).
           05  WS-CLAIM-AMOUNT        PIC S9(7)V99 COMP-3.
           05  WS-CLAIM-TYPE          PIC X(2).

      *--- These fields appear to be dead code from removed feature ---
       01  WS-OLD-RATING-AREA.
           05  WS-OLD-RATE-TABLE      PIC X(4).
           05  WS-OLD-RATE-FACTOR     PIC S9(3)V9(4) COMP-3.
           05  WS-OLD-RATE-DATE       PIC 9(6).
           05  WS-OLD-RATE-CODE       PIC X(2).

      *--- Also potentially dead - no PERFORM references found ---
       01  WS-CONVERSION-WORK.
           05  WS-CONV-INPUT          PIC X(80).
           05  WS-CONV-OUTPUT         PIC X(80).
           05  WS-CONV-LENGTH         PIC S9(4) COMP.

To detect dead code systematically, search for every paragraph name in the PROCEDURE DIVISION and verify that each one is referenced by at least one PERFORM, GO TO, or SECTION invocation. Similarly, search for every data name in WORKING-STORAGE and verify that each one appears in at least one procedural statement.

Control Flow Analysis

Legacy COBOL control flow can be extraordinarily difficult to follow, particularly in programs that use GO TO statements extensively. The key technique is to build a control flow graph, either on paper or using tools.

       PROCEDURE DIVISION.
       MAIN-PARA.
           OPEN INPUT CLAIM-INPUT POLICY-MASTER
           OPEN OUTPUT ERROR-REPORT VALID-CLAIMS.
           READ CLAIM-INPUT
               AT END GO TO EOJ-PARA.
           GO TO PROCESS-CLAIM.

       PROCESS-CLAIM.
           IF WS-CLAIM-TYPE = 'AU'
               GO TO AUTO-CLAIM
           ELSE IF WS-CLAIM-TYPE = 'HO'
               GO TO HOME-CLAIM
           ELSE IF WS-CLAIM-TYPE = 'LF'
               GO TO LIFE-CLAIM
           ELSE
               GO TO INVALID-TYPE.

       AUTO-CLAIM.
           PERFORM VALIDATE-AUTO-POLICY.
           IF WS-VALID-FLAG = 'Y'
               GO TO WRITE-VALID
           ELSE
               GO TO WRITE-ERROR.

       HOME-CLAIM.
           PERFORM VALIDATE-HOME-POLICY.
           IF WS-VALID-FLAG = 'Y'
               GO TO WRITE-VALID
           ELSE
               GO TO WRITE-ERROR.

       LIFE-CLAIM.
           PERFORM VALIDATE-LIFE-POLICY.
           IF WS-VALID-FLAG = 'Y'
               GO TO WRITE-VALID
           ELSE
               GO TO WRITE-ERROR.

       INVALID-TYPE.
           MOVE 'INVALID CLAIM TYPE' TO WS-ERROR-MSG.
           GO TO WRITE-ERROR.

       WRITE-VALID.
           WRITE VALID-CLAIM-RECORD FROM WS-CLAIM-RECORD.
           GO TO READ-NEXT.

       WRITE-ERROR.
           WRITE ERROR-RECORD FROM WS-ERROR-LINE.
           ADD 1 TO WS-ERROR-COUNT.
           GO TO READ-NEXT.

       READ-NEXT.
           READ CLAIM-INPUT
               AT END GO TO EOJ-PARA.
           GO TO PROCESS-CLAIM.

       EOJ-PARA.
           CLOSE CLAIM-INPUT POLICY-MASTER
                 ERROR-REPORT VALID-CLAIMS.
           STOP RUN.

When tracing this code, draw arrows from each GO TO to its target. You will see that the flow forms a loop: MAIN-PARA leads to PROCESS-CLAIM, which branches to one of several claim type paragraphs, all of which converge at either WRITE-VALID or WRITE-ERROR, both of which go to READ-NEXT, which loops back to PROCESS-CLAIM or exits to EOJ-PARA. Understanding this loop structure is essential before any modification.


39.3 Documentation Archaeology

When working with undocumented or poorly documented legacy systems, programmers must reconstruct the intent of the code through a process best described as documentation archaeology. This involves examining multiple sources of evidence to understand not just what the code does but why it does it.

Sources of Evidence

Inline comments. Even sparse comments provide clues. A comment like *FIX FOR STATE REGULATION 42-B reveals that a particular code block implements a specific legal requirement, which can be researched independently.

Data names. COBOL's verbose naming convention is an advantage during archaeology. A field named WS-POLICYHOLDER-DEDUCTIBLE-AMT is self-documenting in a way that a variable named x or tmp never could be.

Copybook contents. Shared copybooks define data structures used across multiple programs. Examining which copybooks a program uses reveals its data dependencies and suggests its role in the larger system.

JCL job streams. The JCL that executes a COBOL program reveals its inputs, outputs, and position in the batch processing sequence. A program that runs after the daily transaction sort but before the master file update is clearly part of the transaction processing pipeline.

Modification history. Change management records, even informal ones in program headers, reveal the evolution of requirements. Each modification tells a story about a business need that drove a code change.

Recovering Business Rules

The most valuable archaeological work is recovering embedded business rules. Consider this code fragment from an insurance claims processing system:

       CALCULATE-DEDUCTIBLE.
           IF WS-POLICY-TYPE = 'HO3' OR 'HO5'
               IF WS-CLAIM-DATE >= 20050829
                  AND WS-STATE-CODE = 'LA' OR 'MS' OR 'AL' OR 'FL'
                   IF WS-PERIL-CODE = 'WND' OR 'HUR'
                       COMPUTE WS-DEDUCTIBLE =
                           WS-DWELLING-COVERAGE * WS-HURR-DEDUCT-PCT
                   ELSE
                       MOVE WS-STD-DEDUCTIBLE TO WS-DEDUCTIBLE
                   END-IF
               ELSE
                   MOVE WS-STD-DEDUCTIBLE TO WS-DEDUCTIBLE
               END-IF
           ELSE
               MOVE WS-STD-DEDUCTIBLE TO WS-DEDUCTIBLE
           END-IF.

This code tells a specific story. The date 20050829 is August 29, 2005, the date Hurricane Katrina made landfall. After Katrina, insurance companies in Gulf Coast states (Louisiana, Mississippi, Alabama, Florida) implemented percentage-based hurricane deductibles for homeowner policies (HO3 and HO5 forms), replacing the flat dollar deductible used for other perils. This single paragraph encodes both a business rule and its historical context. A replacement system must preserve this logic, and understanding the "why" behind it is essential for correct implementation.

Building a Program Inventory

For large modernization efforts, create a systematic inventory of all programs, documenting for each one:

  • Program name and size (lines of code)
  • Input and output files or databases
  • Copybooks used
  • Programs it calls (via CALL statements)
  • Programs that call it
  • CICS transactions associated with it (if online)
  • Batch jobs that execute it (from JCL)
  • Last modification date and author
  • Business function performed

This inventory becomes the foundation for all modernization planning.


39.4 Common Legacy Patterns and Anti-Patterns

Legacy COBOL systems exhibit recurring patterns that make maintenance difficult. Recognizing these patterns is the first step toward addressing them.

Pervasive GO TO Usage

The most common legacy pattern is the use of GO TO as the primary control flow mechanism. Before the structured programming movement of the late 1970s and early 1980s, GO TO was the standard way to direct program flow in COBOL.

      *================================================================*
      * LEGACY PATTERN: GO TO-based control flow                       *
      * This is typical of code written before 1985                    *
      *================================================================*
       PROCEDURE DIVISION.
       000-START.
           OPEN INPUT TRANS-FILE MASTER-FILE.
           OPEN OUTPUT NEW-MASTER REPORT-FILE.
           READ MASTER-FILE INTO WS-MASTER-REC
               AT END MOVE HIGH-VALUES TO WS-MASTER-KEY
           END-READ.
           READ TRANS-FILE INTO WS-TRANS-REC
               AT END MOVE HIGH-VALUES TO WS-TRANS-KEY
           END-READ.
           GO TO 100-COMPARE.

       100-COMPARE.
           IF WS-MASTER-KEY = HIGH-VALUES
              AND WS-TRANS-KEY = HIGH-VALUES
               GO TO 900-EOJ.
           IF WS-TRANS-KEY < WS-MASTER-KEY
               GO TO 200-NEW-ACCOUNT.
           IF WS-TRANS-KEY = WS-MASTER-KEY
               GO TO 300-UPDATE.
           IF WS-TRANS-KEY > WS-MASTER-KEY
               GO TO 400-WRITE-MASTER.

       200-NEW-ACCOUNT.
           MOVE WS-TRANS-REC TO WS-MASTER-REC.
           MOVE 'N' TO WS-NEW-ACCT-FLAG.
           PERFORM 350-APPLY-TRANS.
           GO TO 500-READ-TRANS.

       300-UPDATE.
           PERFORM 350-APPLY-TRANS.
           GO TO 500-READ-TRANS.

       350-APPLY-TRANS.
           IF WS-TRANS-TYPE = 'D'
               ADD WS-TRANS-AMOUNT TO WS-MASTER-BALANCE
           ELSE IF WS-TRANS-TYPE = 'W'
               SUBTRACT WS-TRANS-AMOUNT FROM WS-MASTER-BALANCE
               IF WS-MASTER-BALANCE < ZERO
                   MOVE 'O' TO WS-OVERDRAFT-FLAG
                   GO TO 360-OVERDRAFT
               END-IF
           ELSE IF WS-TRANS-TYPE = 'I'
               COMPUTE WS-MASTER-BALANCE = WS-MASTER-BALANCE *
                   (1 + WS-INTEREST-RATE)
           ELSE
               MOVE 'INVALID TRANS TYPE' TO WS-ERROR-MSG
               GO TO 370-ERROR.
           GO TO 380-TRANS-DONE.

       360-OVERDRAFT.
           ADD 1 TO WS-OVERDRAFT-COUNT.
           COMPUTE WS-OVERDRAFT-FEE = 35.00.
           SUBTRACT WS-OVERDRAFT-FEE FROM WS-MASTER-BALANCE.
           WRITE REPORT-REC FROM WS-OVERDRAFT-LINE.
           GO TO 380-TRANS-DONE.

       370-ERROR.
           ADD 1 TO WS-ERROR-COUNT.
           WRITE REPORT-REC FROM WS-ERROR-LINE.
           GO TO 380-TRANS-DONE.

       380-TRANS-DONE.
           EXIT.

       400-WRITE-MASTER.
           WRITE NEW-MASTER-REC FROM WS-MASTER-REC.
           READ MASTER-FILE INTO WS-MASTER-REC
               AT END MOVE HIGH-VALUES TO WS-MASTER-KEY.
           GO TO 100-COMPARE.

       500-READ-TRANS.
           READ TRANS-FILE INTO WS-TRANS-REC
               AT END MOVE HIGH-VALUES TO WS-TRANS-KEY.
           GO TO 100-COMPARE.

       900-EOJ.
           CLOSE TRANS-FILE MASTER-FILE
                 NEW-MASTER REPORT-FILE.
           STOP RUN.

This code implements a classic sequential file matching algorithm, but the GO TO statements make it difficult to reason about the flow. The program jumps between paragraphs in a non-linear fashion, and understanding the complete processing cycle for a single transaction requires tracing through multiple jumps.

ALTER Statement

The ALTER statement is one of the most dangerous features ever included in a programming language. It dynamically changes the target of a GO TO statement at runtime:

      *================================================================*
      * LEGACY ANTI-PATTERN: ALTER statement                           *
      * This makes control flow unknowable from static analysis        *
      *================================================================*
       PROCEDURE DIVISION.
       START-PARA.
           ALTER DISPATCH-PARA TO PROCEED TO FIRST-TIME-INIT.
           GO TO DISPATCH-PARA.

       DISPATCH-PARA.
           GO TO NORMAL-PROCESS.

       FIRST-TIME-INIT.
           PERFORM LOAD-TABLES.
           PERFORM OPEN-FILES.
           ALTER DISPATCH-PARA TO PROCEED TO NORMAL-PROCESS.
           GO TO DISPATCH-PARA.

       NORMAL-PROCESS.
           PERFORM READ-INPUT.
           IF WS-END-OF-FILE = 'Y'
               ALTER DISPATCH-PARA TO PROCEED TO CLEANUP
               GO TO DISPATCH-PARA
           END-IF.
           PERFORM PROCESS-RECORD.
           GO TO DISPATCH-PARA.

       CLEANUP.
           PERFORM CLOSE-FILES.
           STOP RUN.

The ALTER statement makes the GO TO at DISPATCH-PARA point to different locations depending on the runtime state. This is self-modifying code in effect, and it is nearly impossible to analyze statically. Any program containing ALTER statements should be refactored as a high priority.

Deeply Nested IF Statements

Before the EVALUATE statement was introduced in COBOL-85, complex conditional logic was expressed through deeply nested IF statements:

      *================================================================*
      * LEGACY ANTI-PATTERN: Deeply nested IFs                         *
      * Common before EVALUATE was available                           *
      *================================================================*
       DETERMINE-PREMIUM.
           IF WS-POLICY-TYPE = 'AUTO'
               IF WS-DRIVER-AGE < 25
                   IF WS-GENDER = 'M'
                       IF WS-ACCIDENTS > 0
                           MOVE 2850.00 TO WS-BASE-PREMIUM
                       ELSE
                           IF WS-TICKETS > 2
                               MOVE 2200.00 TO WS-BASE-PREMIUM
                           ELSE
                               MOVE 1800.00 TO WS-BASE-PREMIUM
                           END-IF
                       END-IF
                   ELSE
                       IF WS-ACCIDENTS > 0
                           MOVE 2400.00 TO WS-BASE-PREMIUM
                       ELSE
                           MOVE 1500.00 TO WS-BASE-PREMIUM
                       END-IF
                   END-IF
               ELSE IF WS-DRIVER-AGE < 65
                   IF WS-GENDER = 'M'
                       IF WS-ACCIDENTS > 0
                           MOVE 1800.00 TO WS-BASE-PREMIUM
                       ELSE
                           MOVE 1200.00 TO WS-BASE-PREMIUM
                       END-IF
                   ELSE
                       IF WS-ACCIDENTS > 0
                           MOVE 1600.00 TO WS-BASE-PREMIUM
                       ELSE
                           MOVE 1000.00 TO WS-BASE-PREMIUM
                       END-IF
                   END-IF
               ELSE
                   IF WS-ACCIDENTS > 0
                       MOVE 2100.00 TO WS-BASE-PREMIUM
                   ELSE
                       MOVE 1400.00 TO WS-BASE-PREMIUM
                   END-IF
               END-IF
           ELSE IF WS-POLICY-TYPE = 'HOME'
               IF WS-CONSTRUCTION = 'FRAME'
                   IF WS-ROOF-AGE > 15
                       MOVE 2200.00 TO WS-BASE-PREMIUM
                   ELSE
                       MOVE 1600.00 TO WS-BASE-PREMIUM
                   END-IF
               ELSE
                   IF WS-ROOF-AGE > 20
                       MOVE 1800.00 TO WS-BASE-PREMIUM
                   ELSE
                       MOVE 1200.00 TO WS-BASE-PREMIUM
                   END-IF
               END-IF
           END-IF.

This nested structure is difficult to read, difficult to modify (adding a new category requires careful attention to bracket matching), and prone to logic errors. The END-IF scope terminators were not available in COBOL-74, making such code even harder to follow in older programs that used period-terminated IF statements.

Monolithic Programs

Some legacy programs contain thousands or even tens of thousands of lines in a single program, with little or no modular structure:

      *================================================================*
      * LEGACY ANTI-PATTERN: Monolithic program                        *
      * This program handles ALL claims processing in one unit         *
      * Actual programs of 15,000+ lines are not uncommon              *
      *================================================================*
       IDENTIFICATION DIVISION.
       PROGRAM-ID. CLMSMSTR.
      * THIS PROGRAM:
      *   - VALIDATES CLAIMS
      *   - CHECKS POLICY COVERAGE
      *   - CALCULATES DEDUCTIBLES
      *   - APPLIES STATE REGULATIONS
      *   - DETERMINES PAYMENT AMOUNTS
      *   - GENERATES CHECKS
      *   - PRODUCES REPORTS
      *   - UPDATES CLAIM HISTORY
      *   - SENDS NOTIFICATIONS
      * ALL IN ONE 18,000 LINE PROGRAM

Such programs violate every principle of modular design. They are difficult to understand, impossible to test in isolation, and dangerous to modify because a change in one section may have unintended effects on distant sections that share data through WORKING-STORAGE.


39.5 Refactoring Techniques

Refactoring is the process of restructuring code without changing its external behavior. For legacy COBOL, refactoring typically proceeds through several well-defined transformations.

Structured Programming Transformation

The most fundamental refactoring replaces GO TO-based control flow with structured PERFORM statements. Here is the GO TO-based bank transaction program from Section 39.4, refactored into structured form:

      *================================================================*
      * REFACTORED: Structured version of sequential update program     *
      * GO TO statements eliminated, logic preserved exactly            *
      *================================================================*
       PROCEDURE DIVISION.
       0000-MAIN-PROCESS.
           PERFORM 1000-INITIALIZE
           PERFORM 2000-PROCESS-FILES
               UNTIL WS-MASTER-KEY = HIGH-VALUES
               AND   WS-TRANS-KEY  = HIGH-VALUES
           PERFORM 9000-TERMINATE
           STOP RUN
           .

       1000-INITIALIZE.
           OPEN INPUT  TRANS-FILE MASTER-FILE
           OPEN OUTPUT NEW-MASTER REPORT-FILE
           PERFORM 1100-READ-MASTER
           PERFORM 1200-READ-TRANS
           .

       1100-READ-MASTER.
           READ MASTER-FILE INTO WS-MASTER-REC
               AT END MOVE HIGH-VALUES TO WS-MASTER-KEY
           END-READ
           .

       1200-READ-TRANS.
           READ TRANS-FILE INTO WS-TRANS-REC
               AT END MOVE HIGH-VALUES TO WS-TRANS-KEY
           END-READ
           .

       2000-PROCESS-FILES.
           EVALUATE TRUE
               WHEN WS-TRANS-KEY < WS-MASTER-KEY
                   PERFORM 3000-ADD-NEW-ACCOUNT
               WHEN WS-TRANS-KEY = WS-MASTER-KEY
                   PERFORM 4000-UPDATE-ACCOUNT
               WHEN WS-TRANS-KEY > WS-MASTER-KEY
                   PERFORM 5000-WRITE-UNCHANGED-MASTER
           END-EVALUATE
           .

       3000-ADD-NEW-ACCOUNT.
           MOVE WS-TRANS-REC TO WS-MASTER-REC
           MOVE 'N' TO WS-NEW-ACCT-FLAG
           PERFORM 4100-APPLY-TRANSACTION
           PERFORM 1200-READ-TRANS
           .

       4000-UPDATE-ACCOUNT.
           PERFORM 4100-APPLY-TRANSACTION
           PERFORM 1200-READ-TRANS
           .

       4100-APPLY-TRANSACTION.
           EVALUATE WS-TRANS-TYPE
               WHEN 'D'
                   ADD WS-TRANS-AMOUNT TO WS-MASTER-BALANCE
               WHEN 'W'
                   PERFORM 4200-PROCESS-WITHDRAWAL
               WHEN 'I'
                   COMPUTE WS-MASTER-BALANCE = WS-MASTER-BALANCE *
                       (1 + WS-INTEREST-RATE)
               WHEN OTHER
                   PERFORM 4300-HANDLE-INVALID-TRANS
           END-EVALUATE
           .

       4200-PROCESS-WITHDRAWAL.
           SUBTRACT WS-TRANS-AMOUNT FROM WS-MASTER-BALANCE
           IF WS-MASTER-BALANCE < ZERO
               ADD 1 TO WS-OVERDRAFT-COUNT
               COMPUTE WS-OVERDRAFT-FEE = 35.00
               SUBTRACT WS-OVERDRAFT-FEE FROM WS-MASTER-BALANCE
               MOVE 'O' TO WS-OVERDRAFT-FLAG
               WRITE REPORT-REC FROM WS-OVERDRAFT-LINE
           END-IF
           .

       4300-HANDLE-INVALID-TRANS.
           MOVE 'INVALID TRANS TYPE' TO WS-ERROR-MSG
           ADD 1 TO WS-ERROR-COUNT
           WRITE REPORT-REC FROM WS-ERROR-LINE
           .

       5000-WRITE-UNCHANGED-MASTER.
           WRITE NEW-MASTER-REC FROM WS-MASTER-REC
           PERFORM 1100-READ-MASTER
           .

       9000-TERMINATE.
           CLOSE TRANS-FILE MASTER-FILE
                 NEW-MASTER REPORT-FILE
           .

Notice the key changes in this refactoring:

  1. A clear main loop replaces the implicit loop formed by GO TO chains. The PERFORM...UNTIL in 0000-MAIN-PROCESS makes the iteration structure explicit.
  2. EVALUATE replaces cascading IF/GO TO. The transaction type dispatch in 4100-APPLY-TRANSACTION and the file comparison in 2000-PROCESS-FILES use EVALUATE for clear, readable branching.
  3. Each paragraph has one entry and one exit. No paragraph transfers control to another via GO TO. Every paragraph returns to its caller naturally.
  4. Hierarchical numbering makes the call structure visible. 4100 and 4200 are called from within the 4000-level processing, indicating their subordinate role.
  5. The program reads top-down. You can understand the overall structure from 0000-MAIN-PROCESS without reading any other paragraph.

Paragraph Extraction

Large inline code blocks should be extracted into named paragraphs. This transformation improves readability and enables reuse. As discussed in Chapter 21 on coding standards, well-named paragraphs serve as executable documentation.

Before extraction:

       PROCESS-CLAIM.
      *    --- 45 lines of validation logic ---
           IF WS-CLAIM-AMOUNT > ZERO
               IF WS-CLAIM-AMOUNT <= WS-POLICY-LIMIT
                   IF WS-CLAIM-DATE >= WS-POLICY-EFF-DATE
                       IF WS-CLAIM-DATE <= WS-POLICY-EXP-DATE
                           MOVE 'Y' TO WS-CLAIM-VALID
                       ELSE
                           MOVE 'CLAIM AFTER EXPIRY'
                               TO WS-REJECT-REASON
                           MOVE 'N' TO WS-CLAIM-VALID
                       END-IF
                   ELSE
                       MOVE 'CLAIM BEFORE EFFECTIVE'
                           TO WS-REJECT-REASON
                       MOVE 'N' TO WS-CLAIM-VALID
                   END-IF
               ELSE
                   MOVE 'EXCEEDS POLICY LIMIT'
                       TO WS-REJECT-REASON
                   MOVE 'N' TO WS-CLAIM-VALID
               END-IF
           ELSE
               MOVE 'ZERO OR NEGATIVE AMOUNT'
                   TO WS-REJECT-REASON
               MOVE 'N' TO WS-CLAIM-VALID
           END-IF
      *    --- 30 more lines of processing ---
           .

After extraction:

       PROCESS-CLAIM.
           PERFORM 3100-VALIDATE-CLAIM-AMOUNT
           PERFORM 3200-VALIDATE-CLAIM-DATES
           IF WS-CLAIM-VALID = 'Y'
               PERFORM 3300-CALCULATE-PAYMENT
               PERFORM 3400-UPDATE-CLAIM-HISTORY
           ELSE
               PERFORM 3500-WRITE-REJECTION
           END-IF
           .

       3100-VALIDATE-CLAIM-AMOUNT.
           MOVE 'Y' TO WS-CLAIM-VALID
           IF WS-CLAIM-AMOUNT NOT > ZERO
               MOVE 'ZERO OR NEGATIVE AMOUNT'
                   TO WS-REJECT-REASON
               MOVE 'N' TO WS-CLAIM-VALID
           ELSE IF WS-CLAIM-AMOUNT > WS-POLICY-LIMIT
               MOVE 'EXCEEDS POLICY LIMIT'
                   TO WS-REJECT-REASON
               MOVE 'N' TO WS-CLAIM-VALID
           END-IF
           .

       3200-VALIDATE-CLAIM-DATES.
           IF WS-CLAIM-VALID = 'Y'
               IF WS-CLAIM-DATE < WS-POLICY-EFF-DATE
                   MOVE 'CLAIM BEFORE EFFECTIVE'
                       TO WS-REJECT-REASON
                   MOVE 'N' TO WS-CLAIM-VALID
               ELSE IF WS-CLAIM-DATE > WS-POLICY-EXP-DATE
                   MOVE 'CLAIM AFTER EXPIRY'
                       TO WS-REJECT-REASON
                   MOVE 'N' TO WS-CLAIM-VALID
               END-IF
           END-IF
           .

The extracted version is longer in total lines but dramatically easier to understand. Each validation step is isolated, named, and independently testable. The PROCESS-CLAIM paragraph now reads as a high-level description of the claims processing workflow.

EVALUATE Replacement for Nested IFs

The deeply nested premium calculation from Section 39.4 can be refactored using EVALUATE with multiple WHEN conditions and a table-driven approach:

      *================================================================*
      * REFACTORED: Table-driven premium calculation                   *
      * Replaces deeply nested IF structure                            *
      *================================================================*
       WORKING-STORAGE SECTION.
       01  WS-RATE-TABLE.
           05  WS-RATE-ENTRY OCCURS 12 TIMES.
               10  WS-RT-POLICY-TYPE   PIC X(4).
               10  WS-RT-AGE-MIN       PIC 9(3).
               10  WS-RT-AGE-MAX       PIC 9(3).
               10  WS-RT-RISK-FACTOR   PIC X(1).
               10  WS-RT-BASE-PREMIUM  PIC S9(7)V99 COMP-3.

       PROCEDURE DIVISION.
       DETERMINE-PREMIUM.
           MOVE ZERO TO WS-BASE-PREMIUM
           PERFORM VARYING WS-RATE-IDX FROM 1 BY 1
               UNTIL WS-RATE-IDX > 12
               OR WS-BASE-PREMIUM > ZERO
               IF WS-POLICY-TYPE = WS-RT-POLICY-TYPE(WS-RATE-IDX)
                   AND WS-DRIVER-AGE >= WS-RT-AGE-MIN(WS-RATE-IDX)
                   AND WS-DRIVER-AGE <= WS-RT-AGE-MAX(WS-RATE-IDX)
                   AND WS-RISK-FACTOR = WS-RT-RISK-FACTOR(WS-RATE-IDX)
                   MOVE WS-RT-BASE-PREMIUM(WS-RATE-IDX)
                       TO WS-BASE-PREMIUM
               END-IF
           END-PERFORM
           IF WS-BASE-PREMIUM = ZERO
               MOVE 'RATE NOT FOUND' TO WS-ERROR-MSG
               PERFORM 9100-HANDLE-RATING-ERROR
           END-IF
           .

       LOAD-RATE-TABLE.
      *    Load rates from file or database instead of hardcoding
           READ RATE-FILE INTO WS-RATE-TABLE
               AT END
                   MOVE 'RATE TABLE EMPTY' TO WS-ERROR-MSG
                   PERFORM 9100-HANDLE-RATING-ERROR
           END-READ
           .

This refactoring not only eliminates the nested IFs but externalizes the premium rates from code into data. Adding a new rate category now requires only a new row in the rate table, not a code change. This is a fundamental improvement in maintainability.


39.6 Modernization Strategies

Modernization is a spectrum of approaches, each with different levels of risk, cost, and transformation depth. The choice of strategy depends on the organization's goals, budget, risk tolerance, and timeline.

The Five Rs of Modernization

Rehost (Lift and Shift). Move the COBOL application from the mainframe to a different platform (Linux, cloud) without changing the code. Products like Micro Focus Enterprise Server and NTT DATA UniKix enable COBOL programs to run on distributed platforms. This approach is the lowest risk and lowest cost but provides the fewest benefits. The application still behaves exactly as it did on the mainframe, with all its architectural limitations intact.

Replatform. Move the application to a new platform and replace some infrastructure components while keeping the business logic intact. For example, replace VSAM files with a relational database, replace CICS screens with web interfaces, or replace JCL batch scheduling with a modern scheduler. The COBOL code is modified to work with the new infrastructure but the core logic is preserved.

Refactor. Restructure the COBOL code to improve its internal design without changing its external behavior. This includes the techniques discussed in Section 39.5: eliminating GO TO statements, extracting paragraphs, replacing nested IFs, and decomposing monolithic programs into modules. Refactoring can be performed on the existing platform or combined with rehosting.

Replace. Discard the legacy application entirely and implement its functionality in a new system, typically using modern languages and architectures. This is the highest-risk option because it requires complete understanding and reimplementation of all business logic. Many replacement projects have failed spectacularly because the legacy system's behavior was more complex than anticipated.

Wrap. Encapsulate the legacy application behind modern interfaces (REST APIs, message queues, web services) without modifying the underlying code. The legacy system becomes a black-box service that can be consumed by modern applications. This approach is often the most pragmatic first step because it enables gradual modernization while preserving the proven business logic.

Risk Assessment

Each strategy carries different risks:

Strategy Risk Level Cost Timeline Business Logic Preservation
Rehost Low Low Months Complete
Replatform Medium Medium 6-18 months High
Refactor Medium Medium 6-24 months Complete
Replace Very High Very High 2-5+ years Must be verified
Wrap Low Low-Medium Weeks-Months Complete

The wrapping approach deserves special attention because it is the most commonly successful first step and provides the foundation for incremental modernization.

Wrapping Legacy COBOL with Modern Interfaces

Here is an example of wrapping a legacy COBOL claims processing program with a CICS web service interface:

      *================================================================*
      * PROGRAM: CLMWSVC                                               *
      * PURPOSE: Web service wrapper for legacy claims validation      *
      * This program receives JSON input via CICS WEB services,        *
      * calls the existing CLMPROC1 program, and returns JSON output   *
      *================================================================*
       IDENTIFICATION DIVISION.
       PROGRAM-ID. CLMWSVC.

       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  WS-REQUEST-BODY          PIC X(4096).
       01  WS-RESPONSE-BODY         PIC X(4096).
       01  WS-REQUEST-LENGTH        PIC S9(8) COMP.
       01  WS-RESPONSE-LENGTH       PIC S9(8) COMP.
       01  WS-HTTP-STATUS           PIC S9(4) COMP.

       01  WS-JSON-CLAIM.
           05  WS-JC-CLAIM-NUMBER   PIC X(10).
           05  WS-JC-POLICY-NUMBER  PIC X(12).
           05  WS-JC-CLAIM-DATE     PIC X(10).
           05  WS-JC-CLAIM-AMOUNT   PIC X(12).
           05  WS-JC-CLAIM-TYPE     PIC X(2).

       01  WS-LEGACY-CLAIM-REC.
           COPY CLMREC01.

       01  WS-LEGACY-RESULT.
           COPY CLMRSLT1.

       PROCEDURE DIVISION.
       0000-MAIN-PROCESS.
           PERFORM 1000-RECEIVE-REQUEST
           PERFORM 2000-PARSE-JSON-INPUT
           PERFORM 3000-MAP-TO-LEGACY-FORMAT
           PERFORM 4000-CALL-LEGACY-PROGRAM
           PERFORM 5000-MAP-FROM-LEGACY-FORMAT
           PERFORM 6000-BUILD-JSON-RESPONSE
           PERFORM 7000-SEND-RESPONSE
           EXEC CICS RETURN END-EXEC
           .

       1000-RECEIVE-REQUEST.
           EXEC CICS WEB RECEIVE
               INTO(WS-REQUEST-BODY)
               LENGTH(WS-REQUEST-LENGTH)
               MAXLENGTH(4096)
           END-EXEC
           .

       2000-PARSE-JSON-INPUT.
           EXEC CICS PUT CONTAINER('DFHJSON-DATA')
               FROM(WS-REQUEST-BODY)
               FLENGTH(WS-REQUEST-LENGTH)
               CHANNEL('CLMCHANL')
           END-EXEC
           EXEC CICS LINK PROGRAM('DFHJSON')
               CHANNEL('CLMCHANL')
           END-EXEC
           EXEC CICS GET CONTAINER('DFHJSON-FIELD')
               INTO(WS-JSON-CLAIM)
               CHANNEL('CLMCHANL')
           END-EXEC
           .

       3000-MAP-TO-LEGACY-FORMAT.
      *    Convert modern formats to legacy expectations
           MOVE WS-JC-CLAIM-NUMBER  TO CLM-CLAIM-NUMBER
           MOVE WS-JC-POLICY-NUMBER TO CLM-POLICY-NUMBER
      *    Convert ISO date to legacy YYYYMMDD
           PERFORM 3100-CONVERT-DATE
      *    Convert decimal string to COMP-3
           PERFORM 3200-CONVERT-AMOUNT
           MOVE WS-JC-CLAIM-TYPE    TO CLM-CLAIM-TYPE
           .

       3100-CONVERT-DATE.
           UNSTRING WS-JC-CLAIM-DATE DELIMITED BY '-'
               INTO WS-DATE-YYYY WS-DATE-MM WS-DATE-DD
           END-UNSTRING
           STRING WS-DATE-YYYY WS-DATE-MM WS-DATE-DD
               DELIMITED SIZE INTO CLM-CLAIM-DATE
           END-STRING
           .

       3200-CONVERT-AMOUNT.
           COMPUTE CLM-CLAIM-AMOUNT =
               FUNCTION NUMVAL(WS-JC-CLAIM-AMOUNT)
           .

       4000-CALL-LEGACY-PROGRAM.
           CALL 'CLMPROC1' USING WS-LEGACY-CLAIM-REC
                                  WS-LEGACY-RESULT
           .

       5000-MAP-FROM-LEGACY-FORMAT.
      *    Convert legacy result fields to modern formats
           MOVE RSL-STATUS-CODE   TO WS-JR-STATUS
           MOVE RSL-STATUS-MSG    TO WS-JR-MESSAGE
           MOVE RSL-PAYMENT-AMT   TO WS-JR-AMOUNT
           .

       6000-BUILD-JSON-RESPONSE.
           STRING '{"status":"' WS-JR-STATUS '",'
                  '"message":"' WS-JR-MESSAGE '",'
                  '"paymentAmount":' WS-JR-AMOUNT '}'
               DELIMITED SIZE INTO WS-RESPONSE-BODY
           END-STRING
           MOVE FUNCTION LENGTH(
               FUNCTION TRIM(WS-RESPONSE-BODY))
               TO WS-RESPONSE-LENGTH
           .

       7000-SEND-RESPONSE.
           MOVE 200 TO WS-HTTP-STATUS
           EXEC CICS WEB SEND
               FROM(WS-RESPONSE-BODY)
               LENGTH(WS-RESPONSE-LENGTH)
               MEDIATYPE('application/json')
               STATUSCODE(WS-HTTP-STATUS)
               STATUSTEXT('OK')
           END-EXEC
           .

This wrapper program makes the legacy CLMPROC1 program accessible as a RESTful web service without modifying the legacy code at all. Modern front-end applications, microservices, and mobile apps can now interact with the legacy business logic through standard HTTP/JSON protocols.


39.7 The Strangler Fig Pattern for COBOL Systems

The Strangler Fig pattern, named by Martin Fowler after tropical strangler fig trees that gradually envelop and replace their host trees, is the most successful strategy for incrementally replacing legacy systems. Rather than attempting a risky big-bang replacement, the legacy system is gradually surrounded by new functionality until the old system can be decommissioned.

Implementing the Pattern

The strategy proceeds in phases:

Phase 1: Intercept. Place an intermediary layer (API gateway, message broker, or routing program) between consumers and the legacy system. All requests flow through this intermediary, which initially routes everything to the legacy system unchanged.

Phase 2: Implement. Build new implementations of specific functions using modern technology. These new implementations handle the same inputs and produce the same outputs as the corresponding legacy functions.

Phase 3: Redirect. Configure the intermediary to route specific functions to the new implementation instead of the legacy system. Run both in parallel to verify identical results.

Phase 4: Retire. Once the new implementation is verified, remove the corresponding legacy code. Repeat phases 2-4 for the next function.

COBOL-Specific Considerations

For COBOL systems, the strangler pattern has several specific implementation considerations:

Transaction boundaries. CICS transactions are natural units for strangling. Each transaction (identified by a four-character transaction ID) can be redirected independently from the legacy COBOL program to a new implementation.

Batch job decomposition. Monolithic batch jobs must be decomposed into smaller units before individual functions can be strangled. This often requires refactoring the batch program first.

Data coupling. Legacy programs typically share data through files (VSAM, sequential) and databases (DB2, IMS). The new implementations must maintain compatibility with the shared data formats throughout the transition period. Dual-write strategies, where both old and new systems update the same data stores, are common during the transition.

      *================================================================*
      * PROGRAM: CLMROUTE                                              *
      * PURPOSE: Strangler fig router for claims processing            *
      * Routes claim types to legacy or new implementation             *
      *================================================================*
       IDENTIFICATION DIVISION.
       PROGRAM-ID. CLMROUTE.

       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  WS-ROUTING-TABLE.
           05  FILLER PIC X(6)  VALUE 'AUNEW '.
           05  FILLER PIC X(6)  VALUE 'HOLEG '.
           05  FILLER PIC X(6)  VALUE 'LFLEG '.
           05  FILLER PIC X(6)  VALUE 'FLNEW '.
           05  FILLER PIC X(6)  VALUE 'MDLEG '.
       01  WS-ROUTE-ENTRIES REDEFINES WS-ROUTING-TABLE.
           05  WS-ROUTE-ENTRY OCCURS 5 TIMES.
               10  WS-ROUTE-TYPE    PIC X(2).
               10  WS-ROUTE-TARGET  PIC X(3).
               10  FILLER           PIC X(1).

       01  WS-ROUTE-FOUND          PIC X     VALUE 'N'.
       01  WS-ROUTE-IDX            PIC 9(2)  COMP.

       PROCEDURE DIVISION.
       0000-ROUTE-CLAIM.
           MOVE 'N' TO WS-ROUTE-FOUND
           PERFORM VARYING WS-ROUTE-IDX FROM 1 BY 1
               UNTIL WS-ROUTE-IDX > 5
               OR WS-ROUTE-FOUND = 'Y'
               IF WS-CLAIM-TYPE = WS-ROUTE-TYPE(WS-ROUTE-IDX)
                   MOVE 'Y' TO WS-ROUTE-FOUND
                   EVALUATE WS-ROUTE-TARGET(WS-ROUTE-IDX)
                       WHEN 'NEW'
                           PERFORM 2000-ROUTE-TO-NEW-SYSTEM
                       WHEN 'LEG'
                           PERFORM 3000-ROUTE-TO-LEGACY
                   END-EVALUATE
               END-IF
           END-PERFORM
           IF WS-ROUTE-FOUND = 'N'
               PERFORM 3000-ROUTE-TO-LEGACY
           END-IF
           .

       2000-ROUTE-TO-NEW-SYSTEM.
      *    Route to new microservice via MQ or HTTP
           EXEC CICS LINK PROGRAM('CLMNEWGW')
               COMMAREA(WS-CLAIM-DATA)
               LENGTH(LENGTH OF WS-CLAIM-DATA)
           END-EXEC
           .

       3000-ROUTE-TO-LEGACY.
      *    Route to existing legacy program
           EXEC CICS LINK PROGRAM('CLMPROC1')
               COMMAREA(WS-CLAIM-DATA)
               LENGTH(LENGTH OF WS-CLAIM-DATA)
           END-EXEC
           .

In this example, auto (AU) and flood (FL) claims have been migrated to the new system, while homeowner (HO), life (LF), and medical (MD) claims still run through the legacy program. The routing table can be updated to redirect additional claim types as their new implementations are completed and verified.


39.8 Data Modernization

Data modernization is often the most challenging aspect of legacy system transformation because data formats are deeply embedded in every program and copybook throughout the system.

EBCDIC to Unicode Conversion

Mainframe systems use EBCDIC (Extended Binary Coded Decimal Interchange Code) character encoding, while modern systems use ASCII or Unicode. The conversion is not a simple character-by-character mapping because EBCDIC and ASCII have different collating sequences and different representations for special characters.

      *================================================================*
      * Data conversion considerations                                 *
      * EBCDIC to ASCII/Unicode mapping issues                         *
      *================================================================*
       WORKING-STORAGE SECTION.
      * EBCDIC lowercase 'a' = X'81', ASCII 'a' = X'61'
      * EBCDIC uppercase 'A' = X'C1', ASCII 'A' = X'41'
      * EBCDIC digits are X'F0'-X'F9'
      * EBCDIC space is X'40', ASCII space is X'20'

      * Signed zoned decimal has sign in last byte:
      * +123 in EBCDIC = X'F1F2C3' (C = positive)
      * -123 in EBCDIC = X'F1F2D3' (D = negative)
      * These have NO ASCII equivalent

       01  WS-EBCDIC-AMOUNT        PIC S9(5)V99.
      * Contains zone bits that encode the sign
      * Direct byte transfer to ASCII system will corrupt the value

       01  WS-COMP3-AMOUNT         PIC S9(7)V99 COMP-3.
      * Packed decimal: +1234567.89 = X'123456789C'
      * 5 bytes store 9 digits plus sign
      * No equivalent in ASCII systems

       01  WS-COMP-FIELD            PIC S9(8) COMP.
      * Binary: mainframe uses big-endian byte order
      * Intel/AMD systems use little-endian
      * Same bit pattern means different numbers on each platform

Packed Decimal to Standard Format Conversion

COMP-3 (packed decimal) is the most common numeric format in COBOL financial applications because it provides exact decimal arithmetic without floating-point rounding errors. During modernization, packed decimal values must be converted to formats supported by the target platform while preserving exact precision.

      *================================================================*
      * PROGRAM: DATACONV                                              *
      * PURPOSE: Convert legacy data formats for modernization         *
      *================================================================*
       IDENTIFICATION DIVISION.
       PROGRAM-ID. DATACONV.

       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  WS-LEGACY-POLICY-REC.
      *    Legacy fixed-format record from VSAM file
           05  LP-POLICY-NUM        PIC X(12).
           05  LP-HOLDER-NAME       PIC X(30).
           05  LP-EFF-DATE          PIC 9(8).
           05  LP-EXP-DATE          PIC 9(8).
           05  LP-PREMIUM-AMT       PIC S9(7)V99 COMP-3.
           05  LP-DEDUCTIBLE        PIC S9(5)V99 COMP-3.
           05  LP-COVERAGE-LIMIT    PIC S9(9)V99 COMP-3.
           05  LP-STATE-CODE        PIC X(2).
           05  LP-AGENT-CODE        PIC X(6).
           05  LP-STATUS-FLAG       PIC X(1).
           05  FILLER               PIC X(20).
      *    Total record length: 106 bytes

       01  WS-MODERN-POLICY-REC.
      *    Modern delimited format for database load
           05  MP-POLICY-NUM        PIC X(12).
           05  MP-DELIMITER-1       PIC X     VALUE '|'.
           05  MP-HOLDER-NAME       PIC X(30).
           05  MP-DELIMITER-2       PIC X     VALUE '|'.
           05  MP-EFF-DATE          PIC X(10).
           05  MP-DELIMITER-3       PIC X     VALUE '|'.
           05  MP-EXP-DATE          PIC X(10).
           05  MP-DELIMITER-4       PIC X     VALUE '|'.
           05  MP-PREMIUM-AMT       PIC X(12).
           05  MP-DELIMITER-5       PIC X     VALUE '|'.
           05  MP-DEDUCTIBLE        PIC X(10).
           05  MP-DELIMITER-6       PIC X     VALUE '|'.
           05  MP-COVERAGE-LIMIT    PIC X(14).
           05  MP-DELIMITER-7       PIC X     VALUE '|'.
           05  MP-STATE-CODE        PIC X(2).
           05  MP-DELIMITER-8       PIC X     VALUE '|'.
           05  MP-AGENT-CODE        PIC X(6).
           05  MP-DELIMITER-9       PIC X     VALUE '|'.
           05  MP-STATUS-FLAG       PIC X(1).

       01  WS-DATE-WORK.
           05  WS-DW-YYYY           PIC X(4).
           05  WS-DW-MM             PIC X(2).
           05  WS-DW-DD             PIC X(2).
       01  WS-DATE-ISO.
           05  WS-DI-YYYY           PIC X(4).
           05  WS-DI-DASH1          PIC X     VALUE '-'.
           05  WS-DI-MM             PIC X(2).
           05  WS-DI-DASH2          PIC X     VALUE '-'.
           05  WS-DI-DD             PIC X(2).

       PROCEDURE DIVISION.
       0000-CONVERT-RECORDS.
           PERFORM 1000-INITIALIZE
           PERFORM 2000-PROCESS-RECORDS
               UNTIL WS-EOF-FLAG = 'Y'
           PERFORM 9000-TERMINATE
           STOP RUN
           .

       2000-PROCESS-RECORDS.
           READ LEGACY-FILE INTO WS-LEGACY-POLICY-REC
               AT END
                   MOVE 'Y' TO WS-EOF-FLAG
                   EXIT PARAGRAPH
           END-READ
           PERFORM 2100-CONVERT-FIELDS
           WRITE MODERN-RECORD FROM WS-MODERN-POLICY-REC
           ADD 1 TO WS-RECORDS-CONVERTED
           .

       2100-CONVERT-FIELDS.
      *    Direct moves for character fields
           MOVE LP-POLICY-NUM    TO MP-POLICY-NUM
           MOVE LP-HOLDER-NAME   TO MP-HOLDER-NAME
           MOVE LP-STATE-CODE    TO MP-STATE-CODE
           MOVE LP-AGENT-CODE    TO MP-AGENT-CODE
           MOVE LP-STATUS-FLAG   TO MP-STATUS-FLAG

      *    Convert dates from YYYYMMDD to YYYY-MM-DD
           MOVE LP-EFF-DATE TO WS-DATE-WORK
           PERFORM 2200-FORMAT-ISO-DATE
           MOVE WS-DATE-ISO TO MP-EFF-DATE

           MOVE LP-EXP-DATE TO WS-DATE-WORK
           PERFORM 2200-FORMAT-ISO-DATE
           MOVE WS-DATE-ISO TO MP-EXP-DATE

      *    Convert COMP-3 to display format
      *    COBOL handles this automatically via MOVE
           MOVE LP-PREMIUM-AMT    TO MP-PREMIUM-AMT
           MOVE LP-DEDUCTIBLE     TO MP-DEDUCTIBLE
           MOVE LP-COVERAGE-LIMIT TO MP-COVERAGE-LIMIT
           .

       2200-FORMAT-ISO-DATE.
           MOVE WS-DW-YYYY TO WS-DI-YYYY
           MOVE WS-DW-MM   TO WS-DI-MM
           MOVE WS-DW-DD   TO WS-DI-DD
           .

Flat Files to Database Migration

Moving from VSAM or sequential flat files to relational databases is a common modernization step. The key challenge is that COBOL programs using flat files often rely on the physical ordering of records, which relational databases do not guarantee.

      *================================================================*
      * Before: VSAM KSDS access                                      *
      *================================================================*
       READ-POLICY-VSAM.
           MOVE WS-POLICY-KEY TO POLICY-RECORD-KEY.
           READ POLICY-FILE INTO WS-POLICY-RECORD
               INVALID KEY
                   MOVE 'POLICY NOT FOUND' TO WS-ERROR-MSG
                   PERFORM 9100-HANDLE-NOT-FOUND
           END-READ.

      *================================================================*
      * After: DB2 SQL access (same logic, different data source)      *
      *================================================================*
       READ-POLICY-DB2.
           EXEC SQL
               SELECT POLICY_NUM, HOLDER_NAME,
                      EFF_DATE, EXP_DATE,
                      PREMIUM_AMT, DEDUCTIBLE,
                      COVERAGE_LIMIT, STATE_CODE,
                      AGENT_CODE, STATUS_FLAG
               INTO :WS-POL-NUM, :WS-HOLDER-NAME,
                    :WS-EFF-DATE, :WS-EXP-DATE,
                    :WS-PREMIUM, :WS-DEDUCTIBLE,
                    :WS-COV-LIMIT, :WS-STATE,
                    :WS-AGENT, :WS-STATUS
               FROM POLICY_MASTER
               WHERE POLICY_NUM = :WS-POLICY-KEY
           END-EXEC

           EVALUATE SQLCODE
               WHEN 0
                   CONTINUE
               WHEN +100
                   MOVE 'POLICY NOT FOUND' TO WS-ERROR-MSG
                   PERFORM 9100-HANDLE-NOT-FOUND
               WHEN OTHER
                   MOVE SQLCODE TO WS-SQL-CODE-DISPLAY
                   STRING 'DB2 ERROR: ' WS-SQL-CODE-DISPLAY
                       DELIMITED SIZE INTO WS-ERROR-MSG
                   PERFORM 9200-HANDLE-DB-ERROR
           END-EVALUATE
           .

39.9 Code Analysis Tools

Manual analysis of millions of lines of code is impractical. Several commercial and open-source tools exist to automate the analysis of legacy COBOL systems.

IBM Application Discovery and Delivery Intelligence (ADDI)

IBM ADDI (formerly IBM Application Discovery) scans COBOL source code, copybooks, JCL, DB2 DDL, CICS CSD definitions, and other mainframe artifacts to build a comprehensive model of the application. Its key capabilities include:

  • Cross-reference analysis. Identifies which programs use which copybooks, which programs call which other programs, which programs access which files and databases, and which JCL jobs execute which programs.
  • Impact analysis. Given a proposed change to a data field, copybook, or program, identifies all affected components throughout the system.
  • Dead code detection. Identifies unreachable paragraphs, unused data definitions, and programs that are not called by any other program or JCL job.
  • Business rule extraction. Identifies conditional logic patterns that likely represent business rules, extracting them into a catalog that can be reviewed by business analysts.
  • Complexity metrics. Calculates cyclomatic complexity, nesting depth, program size, and other metrics that help prioritize modernization efforts.

Micro Focus Enterprise Analyzer

Micro Focus Enterprise Analyzer provides similar capabilities with particular strength in visualization. Its interactive diagrams show program call graphs, data flow diagrams, and control flow graphs that make complex system architectures comprehensible.

Open-Source Alternatives

For smaller installations or budget-constrained projects, several open-source tools provide partial coverage:

  • GnuCOBOL includes a cross-reference listing compiler option that identifies data name usage.
  • COBOL-lint tools can check for common anti-patterns and coding standard violations.
  • Custom scripts using grep, awk, and Python can extract paragraph names, PERFORM targets, CALL targets, and COPY statements to build basic cross-reference databases.

Using Analysis Output for Modernization Planning

Analysis tool output drives modernization planning by answering critical questions:

  • Which programs are the most complex and risky to modify?
  • Which programs share the most copybooks (indicating tight coupling)?
  • Which programs are called by the most other programs (indicating high impact if changed)?
  • Which programs are never called (candidates for retirement)?
  • Which data fields are used by the most programs (indicating high impact if the format changes)?

39.10 Testing During Modernization

Testing is the single most critical activity during modernization. Every change must be verified to produce exactly the same results as the legacy system. Two primary testing strategies are employed.

Regression Testing

Regression testing captures the legacy system's outputs for a comprehensive set of inputs, then verifies that the modernized system produces identical outputs for the same inputs.

      *================================================================*
      * PROGRAM: REGTEST1                                              *
      * PURPOSE: Regression test harness for claims processing         *
      * Captures before/after results for comparison                   *
      *================================================================*
       IDENTIFICATION DIVISION.
       PROGRAM-ID. REGTEST1.

       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  WS-TEST-CASE.
           05  WS-TC-NUMBER        PIC 9(6).
           05  WS-TC-DESCRIPTION   PIC X(50).
           05  WS-TC-INPUT.
               10  WS-TC-CLAIM-REC PIC X(200).
           05  WS-TC-EXPECTED.
               10  WS-TC-EXP-STATUS PIC X(2).
               10  WS-TC-EXP-AMOUNT PIC S9(7)V99.
               10  WS-TC-EXP-REASON PIC X(30).
           05  WS-TC-ACTUAL.
               10  WS-TC-ACT-STATUS PIC X(2).
               10  WS-TC-ACT-AMOUNT PIC S9(7)V99.
               10  WS-TC-ACT-REASON PIC X(30).

       01  WS-COUNTERS.
           05  WS-TESTS-RUN        PIC 9(6) VALUE ZERO.
           05  WS-TESTS-PASSED     PIC 9(6) VALUE ZERO.
           05  WS-TESTS-FAILED     PIC 9(6) VALUE ZERO.

       PROCEDURE DIVISION.
       0000-RUN-REGRESSION-TESTS.
           PERFORM 1000-INITIALIZE
           PERFORM 2000-EXECUTE-TEST-CASES
               UNTIL WS-EOF-FLAG = 'Y'
           PERFORM 3000-REPORT-RESULTS
           PERFORM 9000-TERMINATE
           STOP RUN
           .

       2000-EXECUTE-TEST-CASES.
           READ TEST-CASE-FILE INTO WS-TEST-CASE
               AT END
                   MOVE 'Y' TO WS-EOF-FLAG
                   EXIT PARAGRAPH
           END-READ
           ADD 1 TO WS-TESTS-RUN

      *    Call the program under test
           MOVE WS-TC-CLAIM-REC TO WS-CLAIM-INPUT-AREA
           CALL 'CLMPROC1' USING WS-CLAIM-INPUT-AREA
                                  WS-CLAIM-RESULT-AREA

      *    Capture actual results
           MOVE RSL-STATUS  TO WS-TC-ACT-STATUS
           MOVE RSL-AMOUNT  TO WS-TC-ACT-AMOUNT
           MOVE RSL-REASON  TO WS-TC-ACT-REASON

      *    Compare expected vs actual
           IF WS-TC-EXPECTED = WS-TC-ACTUAL
               ADD 1 TO WS-TESTS-PASSED
           ELSE
               ADD 1 TO WS-TESTS-FAILED
               PERFORM 2100-LOG-FAILURE
           END-IF
           .

       2100-LOG-FAILURE.
           MOVE WS-TC-NUMBER      TO RPT-TEST-NUMBER
           MOVE WS-TC-DESCRIPTION TO RPT-TEST-DESC
           MOVE WS-TC-EXP-STATUS  TO RPT-EXPECTED-STATUS
           MOVE WS-TC-ACT-STATUS  TO RPT-ACTUAL-STATUS
           MOVE WS-TC-EXP-AMOUNT  TO RPT-EXPECTED-AMOUNT
           MOVE WS-TC-ACT-AMOUNT  TO RPT-ACTUAL-AMOUNT
           WRITE REPORT-LINE FROM WS-FAILURE-DETAIL
           .

       3000-REPORT-RESULTS.
           MOVE WS-TESTS-RUN    TO RPT-TOTAL-RUN
           MOVE WS-TESTS-PASSED TO RPT-TOTAL-PASSED
           MOVE WS-TESTS-FAILED TO RPT-TOTAL-FAILED
           WRITE REPORT-LINE FROM WS-SUMMARY-LINE
           IF WS-TESTS-FAILED > ZERO
               MOVE 8 TO RETURN-CODE
           ELSE
               MOVE 0 TO RETURN-CODE
           END-IF
           .

Parallel Running

Parallel running executes both the legacy and modernized systems simultaneously in production, comparing their outputs in real time. This provides the highest level of confidence but requires significant infrastructure.

The parallel run typically proceeds through stages:

  1. Shadow mode. The modernized system processes the same inputs as the legacy system, but only the legacy system's outputs are used. Differences are logged for investigation.
  2. Verification mode. Both systems process inputs, and an automated comparator verifies that outputs match. If they differ, the legacy system's output is used and the discrepancy is flagged.
  3. Cutover mode. The modernized system's output is used in production, with the legacy system running in parallel as a safety net. If the modernized system produces an error, the legacy system's output can be substituted.
  4. Retirement. After a sufficient period with no discrepancies, the legacy system is decommissioned.

39.11 Migration from COBOL-74/85 to COBOL 2002/2014

Many legacy systems were written to the COBOL-74 or COBOL-85 standards and have never been updated to use features available in COBOL 2002 or COBOL 2014. Migration to modern COBOL standards is a less risky form of modernization that preserves the language while improving the code's structure and capability.

Key Features Available in COBOL 2002/2014

Object-oriented COBOL. COBOL 2002 introduced full object-oriented programming with classes, methods, inheritance, and interfaces. While OO COBOL has not been widely adopted, it enables better modular design for new development.

Free-format source. COBOL 2002 allows free-format source code, eliminating the 72-column line limit and the fixed column positions for area A and area B. This is a significant quality-of-life improvement for programmers.

      *================================================================*
      * COBOL-85 fixed format (columns 7-72 significant)               *
      *================================================================*
       IDENTIFICATION DIVISION.
       PROGRAM-ID. OLDSTYLE.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  WS-CUSTOMER-NAME PIC X(30).
       PROCEDURE DIVISION.
           DISPLAY 'HELLO FROM FIXED FORMAT'.
           STOP RUN.
*> COBOL 2002+ free format (no column restrictions)
IDENTIFICATION DIVISION.
PROGRAM-ID. NEWSTYLE.
DATA DIVISION.
WORKING-STORAGE SECTION.
01  WS-CUSTOMER-NAME PIC X(30).
PROCEDURE DIVISION.
    DISPLAY 'HELLO FROM FREE FORMAT'.
    STOP RUN.

Intrinsic functions expanded. COBOL 2002 and 2014 added numerous intrinsic functions for string handling, date manipulation, mathematical operations, and more. These replace verbose inline code with concise function calls.

      *================================================================*
      * COBOL-85: Manual string reversal                               *
      *================================================================*
       REVERSE-STRING-MANUAL.
           MOVE FUNCTION LENGTH(WS-INPUT-STRING)
               TO WS-STRING-LEN
           MOVE SPACES TO WS-OUTPUT-STRING
           PERFORM VARYING WS-IDX FROM 1 BY 1
               UNTIL WS-IDX > WS-STRING-LEN
               COMPUTE WS-REV-IDX = WS-STRING-LEN - WS-IDX + 1
               MOVE WS-INPUT-STRING(WS-IDX:1)
                   TO WS-OUTPUT-STRING(WS-REV-IDX:1)
           END-PERFORM.

      *================================================================*
      * COBOL 2002+: Intrinsic function                                *
      *================================================================*
       REVERSE-STRING-MODERN.
           MOVE FUNCTION REVERSE(WS-INPUT-STRING)
               TO WS-OUTPUT-STRING.

VALIDATE statement. COBOL 2002 provides the VALIDATE statement for data validation against the rules defined in the data description, reducing the need for manual validation code.

XML and JSON support. COBOL 2014 and vendor extensions provide XML GENERATE, XML PARSE, JSON GENERATE, and JSON PARSE statements for native handling of modern data formats.

      *================================================================*
      * COBOL 2014: Native JSON generation                             *
      *================================================================*
       WORKING-STORAGE SECTION.
       01  WS-POLICY-DATA.
           05  POLICY-NUMBER    PIC X(12)  VALUE 'POL-00012345'.
           05  HOLDER-NAME      PIC X(30)  VALUE 'JANE SMITH'.
           05  PREMIUM-AMOUNT   PIC 9(7)V99 VALUE 1250.00.
           05  STATE-CODE       PIC X(2)   VALUE 'CA'.

       01  WS-JSON-OUTPUT       PIC X(500).
       01  WS-JSON-LENGTH       PIC 9(5).

       PROCEDURE DIVISION.
       GENERATE-JSON.
           JSON GENERATE WS-JSON-OUTPUT
               FROM WS-POLICY-DATA
               COUNT WS-JSON-LENGTH
               ON EXCEPTION
                   DISPLAY 'JSON GENERATION FAILED'
               NOT ON EXCEPTION
                   DISPLAY WS-JSON-OUTPUT(1:WS-JSON-LENGTH)
           END-JSON
           .

Migration Steps

  1. Compile with strictest warnings. Modern COBOL compilers can flag deprecated features and suggest replacements.
  2. Replace period-terminated sentences with explicit scope terminators (END-IF, END-PERFORM, END-READ, and so on). This is the single most impactful change for code clarity.
  3. Replace GO TO with PERFORM. Use the structured programming transformation described in Section 39.5.
  4. Remove ALTER statements. Replace with EVALUATE or conditional PERFORM.
  5. Replace nested IFs with EVALUATE where appropriate.
  6. Add inline PERFORM where appropriate to reduce the number of tiny single-use paragraphs.
  7. Adopt modern data types where compatible with existing data files.

39.12 Case Study: Modernizing a 1980s Insurance Claims System

This case study illustrates the application of techniques discussed throughout this chapter to a realistic insurance claims processing system.

System Profile

Company: Mid-Atlantic Casualty Insurance (fictional) System: Claims Processing System (CPS) Original deployment: 1984 Language: COBOL-74 (partially upgraded to COBOL-85) Platform: IBM z/OS, CICS, DB2, VSAM Size: 2.4 million lines of COBOL across 847 programs Copybooks: 312 shared copybooks CICS transactions: 94 online transactions Batch jobs: 267 nightly batch jobs Developers: Original team of 12 has been replaced multiple times; current team of 6

Initial Assessment

The modernization team began with an analysis using IBM ADDI, which revealed:

  • 147 programs (17%) contained ALTER statements
  • 423 programs (50%) used GO TO as the primary control flow mechanism
  • 89 programs (11%) exceeded 5,000 lines
  • 12 programs exceeded 15,000 lines
  • 34 copybooks were not referenced by any program (dead artifacts)
  • 58 programs were not called by any JCL job or other program (dead programs)
  • Average cyclomatic complexity was 47 (extremely high; generally, values above 20 indicate code that is very difficult to test)

Phase 1: Cleanup (Months 1-3)

The first phase focused on removing dead code and artifacts:

      *================================================================*
      * BEFORE: Claims validation with dead code and ALTER             *
      * Program CLMVAL03 - 2,847 lines                                *
      *================================================================*
       PROCEDURE DIVISION.
       INIT-PARA.
           ALTER PROCESS-DISPATCH TO PROCEED TO FIRST-RUN.
           GO TO PROCESS-DISPATCH.

       PROCESS-DISPATCH.
           GO TO NORMAL-RUN.

       FIRST-RUN.
           OPEN INPUT CLAIM-FILE POLICY-FILE.
           OPEN OUTPUT VALID-FILE ERROR-FILE REPORT-FILE.
           ALTER PROCESS-DISPATCH TO PROCEED TO NORMAL-RUN.
           GO TO PROCESS-DISPATCH.

       NORMAL-RUN.
           READ CLAIM-FILE INTO WS-CLAIM-REC
               AT END GO TO END-OF-JOB.
      *    --- Dead code from removed feature (Y2K remediation) ---
      *    IF WS-CLAIM-DATE-YY < 50
      *        ADD 2000 TO WS-CLAIM-DATE-YY
      *    ELSE
      *        ADD 1900 TO WS-CLAIM-DATE-YY
      *    END-IF
           IF WS-CLAIM-AMT > 0
               IF WS-CLAIM-AMT <= 999999.99
                   GO TO VALIDATE-POLICY
               ELSE
                   MOVE 'AMOUNT EXCEEDS MAXIMUM' TO WS-ERR-MSG
                   GO TO WRITE-ERROR
               END-IF
           ELSE
               MOVE 'INVALID CLAIM AMOUNT' TO WS-ERR-MSG
               GO TO WRITE-ERROR
           END-IF.

       VALIDATE-POLICY.
           READ POLICY-FILE INTO WS-POLICY-REC
               INVALID KEY
                   MOVE 'POLICY NOT ON FILE' TO WS-ERR-MSG
                   GO TO WRITE-ERROR.
           IF WS-POLICY-STATUS NOT = 'A'
               MOVE 'POLICY NOT ACTIVE' TO WS-ERR-MSG
               GO TO WRITE-ERROR.
           GO TO CHECK-COVERAGE.

       CHECK-COVERAGE.
      *    (Additional validation logic follows for 200+ lines)
           GO TO WRITE-VALID.

      *    --- Dead paragraph - old rating code never called ---
       OLD-RATE-CALC.
           MULTIPLY WS-BASE-RATE BY WS-TERR-FACTOR
               GIVING WS-ADJUSTED-RATE.
           ADD WS-SURCHARGE TO WS-ADJUSTED-RATE.
           GO TO WRITE-VALID.

       WRITE-VALID.
           WRITE VALID-REC FROM WS-CLAIM-REC.
           ADD 1 TO WS-VALID-COUNT.
           GO TO PROCESS-DISPATCH.

       WRITE-ERROR.
           WRITE ERROR-REC FROM WS-ERROR-LINE.
           ADD 1 TO WS-ERROR-COUNT.
           GO TO PROCESS-DISPATCH.

       END-OF-JOB.
           CLOSE CLAIM-FILE POLICY-FILE
                 VALID-FILE ERROR-FILE REPORT-FILE.
           STOP RUN.
      *================================================================*
      * AFTER: Claims validation - cleaned and restructured            *
      * Program CLMVAL03 - 1,924 lines (32% reduction)                *
      * Dead code removed, ALTER eliminated, GO TO removed             *
      *================================================================*
       PROCEDURE DIVISION.
       0000-MAIN-PROCESS.
           PERFORM 1000-INITIALIZE
           PERFORM 2000-PROCESS-CLAIMS
               UNTIL WS-EOF-FLAG = 'Y'
           PERFORM 9000-TERMINATE
           STOP RUN
           .

       1000-INITIALIZE.
           OPEN INPUT  CLAIM-FILE POLICY-FILE
           OPEN OUTPUT VALID-FILE ERROR-FILE REPORT-FILE
           INITIALIZE WS-COUNTERS
           .

       2000-PROCESS-CLAIMS.
           PERFORM 2100-READ-CLAIM
           IF WS-EOF-FLAG = 'Y'
               EXIT PARAGRAPH
           END-IF
           PERFORM 3000-VALIDATE-CLAIM
           IF WS-CLAIM-VALID = 'Y'
               PERFORM 5000-WRITE-VALID-CLAIM
           ELSE
               PERFORM 6000-WRITE-ERROR-RECORD
           END-IF
           .

       2100-READ-CLAIM.
           READ CLAIM-FILE INTO WS-CLAIM-REC
               AT END MOVE 'Y' TO WS-EOF-FLAG
           END-READ
           .

       3000-VALIDATE-CLAIM.
           MOVE 'Y' TO WS-CLAIM-VALID
           PERFORM 3100-VALIDATE-AMOUNT
           IF WS-CLAIM-VALID = 'Y'
               PERFORM 3200-VALIDATE-POLICY
           END-IF
           IF WS-CLAIM-VALID = 'Y'
               PERFORM 3300-VALIDATE-COVERAGE
           END-IF
           .

       3100-VALIDATE-AMOUNT.
           IF WS-CLAIM-AMT NOT > ZERO
               MOVE 'INVALID CLAIM AMOUNT' TO WS-ERR-MSG
               MOVE 'N' TO WS-CLAIM-VALID
           ELSE IF WS-CLAIM-AMT > 999999.99
               MOVE 'AMOUNT EXCEEDS MAXIMUM' TO WS-ERR-MSG
               MOVE 'N' TO WS-CLAIM-VALID
           END-IF
           .

       3200-VALIDATE-POLICY.
           READ POLICY-FILE INTO WS-POLICY-REC
               INVALID KEY
                   MOVE 'POLICY NOT ON FILE' TO WS-ERR-MSG
                   MOVE 'N' TO WS-CLAIM-VALID
                   EXIT PARAGRAPH
           END-READ
           IF WS-POLICY-STATUS NOT = 'A'
               MOVE 'POLICY NOT ACTIVE' TO WS-ERR-MSG
               MOVE 'N' TO WS-CLAIM-VALID
           END-IF
           .

       3300-VALIDATE-COVERAGE.
      *    Coverage validation logic (extracted from inline)
           IF WS-CLAIM-DATE < WS-POLICY-EFF-DATE
               OR WS-CLAIM-DATE > WS-POLICY-EXP-DATE
               MOVE 'CLAIM OUTSIDE POLICY PERIOD' TO WS-ERR-MSG
               MOVE 'N' TO WS-CLAIM-VALID
           END-IF
           IF WS-CLAIM-VALID = 'Y'
               IF WS-CLAIM-AMT > WS-POLICY-COV-LIMIT
                   MOVE 'EXCEEDS COVERAGE LIMIT' TO WS-ERR-MSG
                   MOVE 'N' TO WS-CLAIM-VALID
               END-IF
           END-IF
           .

       5000-WRITE-VALID-CLAIM.
           WRITE VALID-REC FROM WS-CLAIM-REC
           ADD 1 TO WS-VALID-COUNT
           .

       6000-WRITE-ERROR-RECORD.
           WRITE ERROR-REC FROM WS-ERROR-LINE
           ADD 1 TO WS-ERROR-COUNT
           .

       9000-TERMINATE.
           PERFORM 9100-PRINT-SUMMARY
           CLOSE CLAIM-FILE POLICY-FILE
                 VALID-FILE ERROR-FILE REPORT-FILE
           .

       9100-PRINT-SUMMARY.
           MOVE WS-VALID-COUNT TO RPT-VALID-COUNT
           MOVE WS-ERROR-COUNT TO RPT-ERROR-COUNT
           COMPUTE RPT-TOTAL-COUNT =
               WS-VALID-COUNT + WS-ERROR-COUNT
           WRITE REPORT-LINE FROM WS-SUMMARY-LINE
           .

Phase 2: API Wrapping (Months 4-8)

The highest-value CICS transactions were wrapped with REST API endpoints, enabling new web and mobile applications to access claims data without going through 3270 terminal screens. The wrapping approach from Section 39.6 was applied to 23 key transactions.

Phase 3: Data Modernization (Months 6-14)

The VSAM policy master file was migrated to DB2 tables. This required modifying 134 programs that accessed the policy file. Each modification replaced VSAM READ/WRITE/REWRITE/DELETE statements with equivalent SQL operations. The programs were modified in batches of 15-20, with each batch undergoing full regression testing before the next batch began.

Phase 4: Strangler Fig Migration (Months 12-30)

New claim types (cyber insurance, rideshare coverage) were implemented entirely in Java microservices. The strangler fig router directed these claim types to the new services while existing claim types continued to be processed by the refactored COBOL programs. Over 18 months, 40% of claim types were migrated to the new platform.

Results

After 30 months of incremental modernization:

  • Code volume reduced from 2.4 million to 1.7 million lines (29% reduction through dead code removal and refactoring)
  • ALTER statements reduced from 147 programs to 0
  • GO TO usage reduced from 423 programs to 31 (programs scheduled for replacement)
  • Average cyclomatic complexity reduced from 47 to 18
  • All 94 CICS transactions available as REST APIs
  • 40% of claims processing running on modern Java microservices
  • Zero production outages during the entire modernization effort
  • The legacy COBOL system continued to process 100% of production claims throughout the project, with no business disruption

Lessons Learned

  1. Start with analysis, not coding. The initial ADDI analysis was the most valuable investment, identifying 58 dead programs that could be immediately retired and 34 dead copybooks that could be removed.

  2. Regression testing is non-negotiable. Every change, no matter how small, was regression tested. The team maintained a test suite of 24,000 test cases that ran in 45 minutes on the mainframe.

  3. Preserve the team's knowledge. The six COBOL programmers were not replaced; they were retrained to work alongside the Java developers. Their knowledge of the business rules and system behavior was irreplaceable.

  4. Modernization is a continuous process, not a project. The modernization effort was designed to produce incremental value at every stage, not to deliver a big-bang transformation at the end.

  5. Do not underestimate data conversion. Converting from VSAM to DB2 consumed more effort than any other single activity because data format dependencies were pervasive and subtle. A field that appeared to be a simple PIC X(8) date could contain packed zeros, spaces, or special sentinel values that the programs checked for in non-obvious ways.


Summary

Legacy COBOL system maintenance and modernization is a discipline that combines technical skill with pragmatic engineering judgment. The 220 billion lines of COBOL in production worldwide will not be replaced overnight, and they do not need to be. Through systematic analysis, careful refactoring, strategic wrapping, and incremental replacement using patterns like the strangler fig, organizations can modernize their COBOL systems while preserving the business logic that drives their operations.

The key principles of successful modernization are: understand before you change, test everything, preserve business logic, proceed incrementally, and remember that working software has value that must not be destroyed in the pursuit of technological currency. As discussed throughout this textbook and reinforced by the coding standards in Chapter 21, well-structured COBOL code is maintainable code, whether it was written in 1984 or 2024.

The next chapter addresses the complementary discipline of testing, quality assurance, and deployment, which provides the safety net that makes all modernization work possible.