Explained: COBOL

Codelooru cobol

Every time a bank processes a payment, there is a reasonable chance a COBOL program is doing the work. Every time an insurance company calculates a premium or a government agency processes a benefit, there is a good chance COBOL is involved. The language was designed in 1959. It is still running, unchanged in many cases, in production today.

Most engineers have heard of COBOL and formed an opinion of it without ever reading a line of it. The reputation is unfair. COBOL is verbose, yes. It has quirks that reflect decisions made when 64 KB of memory was an extravagance. But it is also precise, readable, and extremely good at the thing it was designed to do: process large volumes of structured business data reliably.

This post explains what COBOL actually is, how a program is structured, how data is defined, and how it connects to CICS. It is not a complete language tutorial. It is enough to make the rest of the Mainframe Decoded series make sense when code appears.


What COBOL Is

COBOL stands for Common Business-Oriented Language. It was created in 1959 by a committee called CODASYL, partly based on earlier work by Grace Hopper. The goal was a language that non-technical business staff could read and understand, that could run on any vendor's hardware, and that was specifically designed for the kind of data processing businesses needed: reading records, transforming them, writing them somewhere else.

COBOL is a compiled, procedural language. A COBOL source file is passed through a compiler (on z/OS, IBM's Enterprise COBOL compiler) to produce a load module, which is a native executable. There is no runtime interpreter. The compiled program runs directly on the machine. This is one reason COBOL programs are fast at what they do.

The language has been standardized many times: COBOL 68, 74, 85, 2002, 2014. Each revision added features. The COBOL 2002 standard added object-oriented constructs. In practice, most production mainframe COBOL is written in a style closer to COBOL 85, with procedural logic and none of the OO features. The OO extensions exist; they are rarely used.

One number that is often cited: there are estimated to be around 220 billion lines of COBOL in production worldwide. That is not a reason to love COBOL. It is a reason to understand it.


The Four Divisions

Every COBOL program is divided into exactly four named sections called divisions, always in the same order. This rigid structure is one of COBOL's most distinctive features. You cannot put code before you have declared data. You cannot reference a file you have not defined. The compiler enforces the structure.

IDENTIFICATION DIVISION Program name, author, date written No executable content. Mandatory. ENVIRONMENT DIVISION File definitions, system configuration Empty in CICS programs (no SELECT allowed) DATA DIVISION All variables, records, and data structures Working-Storage, Linkage, File sections PROCEDURE DIVISION All executable logic Paragraphs, sections, CICS commands Key structural rules • Divisions always appear in this order • Every data item must be declared in DATA DIVISION before use • No dynamic typing — every field has a fixed type and length • Logic in PROCEDURE DIVISION can only use declared data items • CICS programs leave ENVIRONMENT DIVISION empty — CICS handles I/O • Programs end with GOBACK or EXEC CICS RETURN in CICS context Figure 1: The four COBOL divisions — always in this order, always present.

IDENTIFICATION DIVISION

The first division identifies the program. The only mandatory entry is `PROGRAM-ID`, which gives the program its name. The name is significant: it must match the load module name that CICS is configured to load. Optional entries like `AUTHOR` and `DATE-WRITTEN` are treated as comments by the compiler. They are widely used in practice because COBOL programs tend to live for decades and the author's name is useful to find 30 years later.

ENVIRONMENT DIVISION

In a batch COBOL program, the Environment Division declares which external files the program uses, using `SELECT` statements that map a logical file name to a physical `DD` name in the JCL. In a CICS program, this division is left empty. CICS manages all file access through its own file control interface, so the `SELECT` mechanism is not used. The division header must still be present; the body is omitted.

DATA DIVISION

The Data Division is where all data is declared. No variable in COBOL can be used without first being defined here. The division is split into sections: the Working-Storage Section holds the program's variables and data structures; the Linkage Section holds data received from callers (including the CICS commarea); the File Section defines record layouts for files (used in batch, not CICS).

PROCEDURE DIVISION

The Procedure Division contains all the executable code. It is organized into named blocks called paragraphs, which group related statements. Paragraphs can be called with `PERFORM`, which is roughly equivalent to a method call. Logic flows sequentially within a paragraph and transfers between paragraphs via `PERFORM` or `GO TO`. CICS commands appear here, embedded between `EXEC CICS` and `END-EXEC`.


Data Definition: PIC, Level Numbers, and Working Storage

COBOL's data declaration syntax looks like nothing else in mainstream programming. Understanding it is essential for reading any COBOL program.

Level Numbers

Every data item in COBOL has a level number from 01 to 49 (with some special values like 77 and 88). Level numbers define a hierarchy. A level 01 item is a top-level record. Level 05, 10, 15 items are fields nested within it. The indentation is meaningful: a higher-numbered level is a child of the nearest lower-numbered level above it.

WORKING-STORAGE SECTION.
01 WS-ACCOUNT-RECORD.
   05 WS-ACCOUNT-NUMBER    PIC 9(10).
   05 WS-ACCOUNT-NAME      PIC X(30).
   05 WS-BALANCE.
      10 WS-BALANCE-POUNDS  PIC 9(8).
      10 WS-BALANCE-PENCE   PIC 9(2).
   05 WS-STATUS-CODE        PIC X(1).
      88 WS-ACTIVE          VALUE 'A'.
      88 WS-CLOSED          VALUE 'C'.

WS-ACCOUNT-RECORD is the top-level group item at level 01. It has four children at level 05. WS-BALANCE is itself a group item containing two level 10 children. WS-STATUS-CODE has two level 88 entries, which are condition names: a COBOL shorthand that lets you write IF WS-ACTIVE instead of IF WS-STATUS-CODE = 'A'.

A group item like WS-ACCOUNT-RECORD occupies contiguous memory equal to the sum of all its children. You can reference the entire record as a single field (useful for copying or clearing it), or you can reference any individual field within it. This is the closest COBOL has to a struct.

The PIC Clause

Every elementary (non-group) data item has a PIC clause (short for PICTURE) that specifies its data type and length. The symbols are:

  • 9: a numeric digit. PIC 9(10) is a 10-digit number.
  • X: an alphanumeric character. PIC X(30) is a 30-character string.
  • A: an alphabetic character (letters and spaces only). Rare in practice.
  • S: a sign. PIC S9(8) is a signed 8-digit number.
  • V: an implied decimal point. PIC 9(6)V99 is a 6-digit integer with 2 decimal places, stored without the decimal point character.
  • COMP / COMP-3: storage format qualifiers. COMP stores numbers in binary. COMP-3 (packed decimal) stores two digits per byte. Both are used to reduce storage and improve arithmetic performance.

The PIC clause is not just documentation: it determines exactly how many bytes a field occupies in memory and how arithmetic operations behave on it. A PIC 9(4) COMP field (a binary halfword) behaves very differently from a PIC 9(4) field (four bytes of character storage).


The Procedure Division: Logic and Flow

COBOL's Procedure Division reads like English, to a fault. The language designers chose English keywords deliberately, believing that business managers should be able to read a COBOL program and verify it matched the business rules. The verbosity is a direct consequence of that decision.

Moving Data

The most common statement in any COBOL program is `MOVE`. It copies data from one field to another:

MOVE WS-ACCOUNT-NUMBER TO WS-DISPLAY-ACCT
MOVE ZEROS             TO WS-BALANCE
MOVE SPACES            TO WS-ACCOUNT-NAME

`ZEROS` and `SPACES` are figurative constants: they fill the target field with zeros or spaces respectively, regardless of the field's length. There is no assignment operator. There is no `=`. Everything is spelled out.

Conditionals and EVALUATE

COBOL has `IF`/`ELSE`/`END-IF` for conditionals. The `EVALUATE` statement is COBOL's equivalent of a switch, and in practice it is more readable than a chain of `IF` statements:

EVALUATE WS-STATUS-CODE
   WHEN 'A'
      PERFORM PROCESS-ACTIVE-ACCOUNT
   WHEN 'C'
      PERFORM PROCESS-CLOSED-ACCOUNT
   WHEN OTHER
      PERFORM HANDLE-UNKNOWN-STATUS
END-EVALUATE

PERFORM and Paragraphs

`PERFORM` calls a named paragraph, optionally with looping:

PERFORM VALIDATE-INPUT
PERFORM PROCESS-RECORD UNTIL WS-EOF = 'Y'
PERFORM VARYING WS-IDX FROM 1 BY 1
        UNTIL WS-IDX > WS-RECORD-COUNT
   PERFORM PROCESS-SINGLE-RECORD
END-PERFORM

Paragraphs in COBOL are not functions. They do not have parameters or return values. All data is shared through Working-Storage. This is one of COBOL's structural weaknesses for large programs: the shared state model makes it hard to reason about what a paragraph does without tracing all the Working-Storage fields it touches.


COBOL in CICS: The Key Differences

A COBOL program running under CICS is not the same as a standalone batch COBOL program. CICS imposes specific rules and provides a different set of services. Understanding the differences is critical before you can read or write any CICS application code.

No File I/O via SELECT

Batch COBOL programs use `SELECT` in the Environment Division and `READ`/`WRITE`/`OPEN`/`CLOSE` verbs to access files. CICS programs do none of this. File access in CICS goes through the CICS file control interface: `EXEC CICS READ`, `EXEC CICS WRITE`, `EXEC CICS REWRITE`. The Environment Division is left empty. The CICS exec interface handles the physical I/O and transaction-aware locking.

The EXEC CICS Interface

Every CICS command is embedded in the Procedure Division between `EXEC CICS` and `END-EXEC`. The CICS translator (a pre-processor that runs before the COBOL compiler) converts these into native COBOL calls to the CICS exec interface. From the compiler's perspective, there are no CICS commands; it sees generated COBOL. This is why CICS COBOL programs require a two-step compilation process.

EXEC CICS READ
   DATASET('ACCOUNTS')
   INTO(WS-ACCOUNT-RECORD)
   RIDFLD(WS-ACCOUNT-NUMBER)
   RESP(WS-CICS-RESP)
END-EXEC

IF WS-CICS-RESP = DFHRESP(NORMAL)
   PERFORM DISPLAY-ACCOUNT
ELSE IF WS-CICS-RESP = DFHRESP(NOTFND)
   PERFORM ACCOUNT-NOT-FOUND
ELSE
   PERFORM HANDLE-UNEXPECTED-ERROR
END-IF

The `RESP` option captures the CICS response code into a Working-Storage field. `DFHRESP(NORMAL)` and `DFHRESP(NOTFND)` are pre-defined constants. Checking `RESP` after every CICS command is standard practice; ignoring it is a common source of bugs.

Working Storage and Reentrancy

In a CICS environment, a single copy of the compiled program code may be shared by many tasks running simultaneously. Each task gets its own private copy of Working-Storage. This is what reentrancy means in practice: the code is read-only and shared; the data is per-task. A CICS COBOL program must never modify itself or rely on static data in the code itself. Everything mutable goes in Working-Storage.

The Commarea and DFHCOMMAREA

When one CICS program calls another via `EXEC CICS LINK`, it can pass a block of data called a commarea (communication area). The called program receives this in its Linkage Section, declared as `DFHCOMMAREA`. The commarea is also used for pseudo-conversational programming: a program can return to CICS while preserving state in the commarea, which CICS passes back when the user's next input arrives. This is how CICS programs maintain the appearance of a conversation without holding a task open between keystrokes.

LINKAGE SECTION.
01 DFHCOMMAREA.
   05 CA-ACCOUNT-NUMBER    PIC 9(10).
   05 CA-LAST-ACTION       PIC X(4).
   05 CA-USER-ID           PIC X(8).
COBOL-CICS PROGRAM STRUCTURE IDENTIFICATION DIVISION. PROGRAM-ID. ACCTINQ. ENVIRONMENT DIVISION. (empty) DATA DIVISION. WORKING-STORAGE SECTION. 01 WS-ACCOUNT-NUMBER PIC 9(10). 01 WS-CICS-RESP PIC S9(8) COMP. LINKAGE SECTION. 01 DFHCOMMAREA PIC X(18). PROCEDURE DIVISION. MAIN-LOGIC. EXEC CICS READ DATASET('ACCOUNTS') INTO(WS-ACCOUNT-RECORD) RESP(WS-CICS-RESP) END-EXEC. Name must match CICS program definition Max 8 characters No SELECT statements — CICS owns file I/O All data declared here before use Working-Storage: per-task private copy COMP fields: binary; faster arithmetic Linkage: receives commarea from caller DFHCOMMAREA: CICS-specific convention All executable logic here Paragraphs are named code blocks EXEC CICS ... END-EXEC for CICS calls CICS translator converts to COBOL calls Always check RESP after every command End with EXEC CICS RETURN END-EXEC Figure 2: A CICS COBOL program’s four divisions, annotated.

A Complete Working Example

Here is a minimal but realistic CICS COBOL program. It reads an account record from a VSAM file and returns. It shows the full structure, Working-Storage layout, CICS commands, and response code handling.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. ACCTINQ.

       ENVIRONMENT DIVISION.

       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01 WS-ACCOUNT-NUMBER     PIC 9(10).
       01 WS-CICS-RESP          PIC S9(8) COMP.
       01 WS-ACCOUNT-RECORD.
          05 WS-ACCT-NUM        PIC 9(10).
          05 WS-ACCT-NAME       PIC X(30).
          05 WS-ACCT-BALANCE    PIC S9(10)V99 COMP-3.
          05 WS-ACCT-STATUS     PIC X(1).
             88 WS-ACCT-ACTIVE  VALUE 'A'.

       LINKAGE SECTION.
       01 DFHCOMMAREA.
          05 CA-ACCT-NUMBER     PIC 9(10).

       PROCEDURE DIVISION.
       MAIN-LOGIC.
           MOVE CA-ACCT-NUMBER TO WS-ACCOUNT-NUMBER

           EXEC CICS READ
               DATASET('ACCOUNTS')
               INTO(WS-ACCOUNT-RECORD)
               RIDFLD(WS-ACCOUNT-NUMBER)
               RESP(WS-CICS-RESP)
           END-EXEC

           EVALUATE WS-CICS-RESP
               WHEN DFHRESP(NORMAL)
                   PERFORM RETURN-ACCOUNT-DATA
               WHEN DFHRESP(NOTFND)
                   PERFORM ACCOUNT-NOT-FOUND
               WHEN OTHER
                   PERFORM HANDLE-ERROR
           END-EVALUATE

           EXEC CICS RETURN END-EXEC.

       RETURN-ACCOUNT-DATA.
           *> Format and send account data back to caller
           EXEC CICS RETURN
               TRANSID('ACCT')
               COMMAREA(WS-ACCOUNT-RECORD)
               LENGTH(43)
           END-EXEC.

       ACCOUNT-NOT-FOUND.
           MOVE SPACES TO WS-ACCOUNT-RECORD
           EXEC CICS RETURN END-EXEC.

       HANDLE-ERROR.
           EXEC CICS ABEND
               ABCODE('AERR')
           END-EXEC.

A few things worth noting in this program. The account number arrives in the commarea from the calling transaction and is moved to Working-Storage before use. The `READ` command uses `RIDFLD` to identify the record by its key field. Response codes are checked with `EVALUATE` rather than nested `IF` statements, which is cleaner for multiple conditions. The program ends each path with an `EXEC CICS RETURN`, which hands control back to CICS rather than falling off the end of the code.

The `*>` prefix on the comment line is modern COBOL comment syntax. Older COBOL programs use an asterisk in column 7, a holdover from the days of punched cards when each card was a fixed 80-column line and column 7 was specifically reserved for the comment indicator.


Where You Encounter COBOL

Every CICS application program in the series is COBOL. When the CICS link modules post covers `EXEC CICS LINK`, the calling and called programs are COBOL. When the CICS storage management post discusses Working-Storage allocation, it is COBOL Working-Storage. When the error handling post covers abend codes, the programs that abend are COBOL programs.

Batch programs on z/OS are also predominantly COBOL. A batch job submitted via JCL will typically execute a COBOL load module, reading from VSAM or sequential files and writing results. The same data structures, the same PIC clauses, the same PERFORM patterns appear in batch as in CICS programs, but without the `EXEC CICS` commands.

COBOL is also increasingly relevant in modernization projects. A team rewriting a mainframe application to Java or Python needs to understand exactly what the COBOL program does before they can replace it. COBOL's explicit data declarations and fixed layouts make it, in some ways, easier to reverse-engineer than languages with dynamic typing and implicit structure.


Summary

COBOL's four-division structure enforces a clean separation between metadata, configuration, data, and logic. Its PIC-clause data declaration model is verbose but precise: every field has a defined type, a fixed length, and a known memory layout. There are no surprises at runtime about what a field contains or how big it is.

In a CICS context, COBOL programs have specific constraints: no SELECT statements, all file I/O via `EXEC CICS` commands, working storage that is per-task and private, and a commarea mechanism for passing data between programs and preserving state across interactions. These constraints exist because CICS, not the COBOL runtime, owns the execution environment.

The language is not elegant by modern standards. But a 40-year-old COBOL program processing mortgage payments is doing exactly what it was written to do, in a language that has not changed around it, on a platform that guarantees its output. That combination of stability and precision is why COBOL is still here.

Part of the Mainframe Decoded series — IBM Z and z/OS, clearly explained for engineers.



×