The Minimum You Need to Know Before Reading COBOL Source Code

· Updated: · · COBOL, Legacy Technology, Business Systems, Maintenance, Mainframe

Revision history (1 updates, last updated Sep 1, 2026)

A log of the changes made to this article. Where a pre-update version was archived, it stays readable at a permanent DOI link.

Retranslated as a full translation of the Japanese original. The previous English version was an abridgement that carried only part of the source, so sections, tables, Mermaid diagrams, figure captions and FAQ entries were missing. All of them have been restored to match the Japanese original, and the technical claims are the same as in the Japanese version. Read the version before this update (DOI: 10.5281/zenodo.21614531)
First published
Cite this article(DOI: 10.5281/zenodo.21614530)

This article is archived on Zenodo. Below are both the DOI that always resolves to the latest version and the DOI pinned to the version you are reading.

Go Komura (2026). The Minimum You Need to Know Before Reading COBOL Source Code. KomuraSoft LLC. https://doi.org/10.5281/zenodo.21614530 https://comcomponent.com/en/blog/2026/03/17/001-cobol-minimum-reading-guide/

DOI (latest version)
10.5281/zenodo.21614530
DOI (this version)
10.5281/zenodo.22217168

Handovers, incident response, maintaining a vendor package. In situations like these, one day a pile of COBOL source code suddenly lands on your desk.

  • The file names end in .cbl or .cpy
  • The variable names are all uppercase
  • Rows of 01, 05, 77, 88
  • Things like PIC S9(7)V99 COMP-3 appear, somewhere between an incantation and accounting software
  • And it is full of COPY, so the file you opened does not even show you the whole picture

This is usually the point where people stall.

But the map you need to read it is not that big. COBOL varies between compilers and products, yet the skeleton you should grasp first when reading an existing business system is largely the same everywhere. With IBM-style and typical business COBOL in mind, this article lays out the minimum set for people who suddenly have to read COBOL source code.

Why you stall and how big the map really isA diagram showing that all-uppercase names, level numbers, notation such as PIC S9(7)V99 COMP-3, and source full of COPY make readers stall, but that the skeleton to grasp first when reading an existing business system is largely common across compilers, so the map is not that big.Unfamiliar notation makes you stallBut the skeleton is common across compilersGet the minimum map firstCOPY everywhere hides the whole picture

Figure 1: What looks like an incantation comes down to a handful of shared concepts.

1. The Conclusion First (In One Breath)

Putting it rather crudely up front, but in a way that actually helps in practice:

  • Before being a language of logic, COBOL is very much a language of record definitions
  • Reading only the PROCEDURE DIVISION gives you half the story. Look at the DATA DIVISION first
  • PIC is the shape of an item; USAGE is how it is represented
  • COMP-3 is packed decimal. It shows up constantly in the world of amounts and counts
  • 88 is not a separate variable; it is a condition-name attached to the values of the preceding item
  • REDEFINES is a mechanism for viewing the same memory in a different shape. It is not a copy
  • If there is a COPY, the source you are looking at is not yet complete. You cannot see the whole until you open the copybooks
  • If you can follow PERFORM, IF, EVALUATE, READ, WRITE, and CALL, you can grasp most of the flow
  • Old source is fixed format, where column positions carry meaning. The whitespace you see is not just decoration1

In short: DIVISION, PIC, USAGE, COMP-3, REDEFINES, OCCURS, 88, COPY, PERFORM. Once you can read these, your odds of getting lost drop considerably.

Knowledge map for this article

Before COBOL is a language of processing steps written in the PROCEDURE DIVISION, it is a language that defines the shape of records in the DATA DIVISION, where each data item gets its shape from the PICTURE clause and its internal representation, such as COMP-3, from the USAGE clause. COMP-3 is packed decimal, which packs two decimal digits into each byte, so it looks like a meaningless sequence of bytes when the file is opened as text. REDEFINES reinterprets the same storage area with a different layout, OCCURS expresses an array, and an 88 level expresses a condition that gives a name to a value of the item immediately above it. Because the COPY statement pulls an external copybook in at compile time, the source file on screen does not show the whole picture on its own, and reading COBOL means following the flow of processing through PERFORM statements and scope terminators and pinning down the external boundaries through FILE STATUS, EXEC SQL, and EXEC CICS.

How to read COBOLDiagram showing how COBOL rests on the DATA DIVISION and the PROCEDURE DIVISION, and how data definition elements such as the PICTURE clause, the USAGE clause, COMP-3, REDEFINES, OCCURS, 88 levels, and the COPY statement relate to control elements such as the PERFORM statement and scope terminators and to external boundary elements such as EBCDIC, FILE STATUS, EXEC SQL, and EXEC CICSusesusesusesusesusesusesusesusesusesusesusesusesusesusesusesusesusesusesusesusesusesCOBOLDATA DIVISIONCOMP-3 (packed decimal)PROCEDURE DIVISIONPICTURE clause (PIC)USAGE clauseCOMP-5REDEFINES clauseOCCURS clauseOCCURS DEPENDING ONLevel 88 (Condition Name)COPY StatementCopybookPERFORM statementScope terminatorMOVE statementFixed format (reference format)IBM Enterprise COBOLEBCDICFILE STATUS ClauseEXEC SQLEXEC CICS

In the diagram a solid line marks a relation that always holds and a dashed line marks a conditional one (the conditions are given per relation on the detail page). The full list of relations (21 in total, with evidence and certainty) and the definitions of the main concepts are collected on the knowledge map detail page (in Japanese). Data: JSON-LD / Turtle

2. Think of COBOL First as a Language About the Shape of Data

If you read it with C# or Java instincts, you will first want to chase the ifs, fors, and function calls. But with COBOL, before going there, it is faster to grasp “what records does this program receive, what records does it produce, and what buffers does it hold?”

A typical business COBOL program flows roughly like this:

  1. Read records from a file or DB
  2. Move them into WORKING-STORAGE items
  3. Branch on conditions
  4. Repack them into another record
  5. Write them out

In other words, layout tends to come before algorithm.

The flow of a typical business COBOL programA diagram showing the flow of a typical business COBOL program, which reads records from a file or DB, moves them into WORKING-STORAGE items, branches on conditions, repacks them into another record, and writes them out.Read a recordMove it into WORKING-STORAGEBranch on conditionsRepack into another recordWrite it out

Figure 2: The record flow is what matters, and the algorithm sits in between.

For example, here is a typical skeleton.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. SAMPLE01.

       ENVIRONMENT DIVISION.
       INPUT-OUTPUT SECTION.
       FILE-CONTROL.
           SELECT SALES-FILE ASSIGN TO ...

       DATA DIVISION.
       FILE SECTION.
       FD  SALES-FILE.
       01  SALES-REC.
           05  SALE-ID       PIC 9(8).
           05  SALE-AMOUNT   PIC S9(7)V99 COMP-3.

       WORKING-STORAGE SECTION.
       01  WS-EOF            PIC X VALUE 'N'.
           88  EOF           VALUE 'Y'.

       PROCEDURE DIVISION.
           PERFORM UNTIL EOF
               READ SALES-FILE
                   AT END
                       SET EOF TO TRUE
                   NOT AT END
                       PERFORM PROCESS-SALE
               END-READ
           END-PERFORM
           STOP RUN.

When reading this code, the first things to look at are the type of SALE-AMOUNT and the meaning of EOF, before the PERFORM. Read COBOL in that order and it suddenly goes quiet.

3. Look at the Four DIVISIONs First

COBOL source is first divided into four large DIVISIONs.

DIVISION What to look at first
IDENTIFICATION DIVISION Program name, old comments, provenance
ENVIRONMENT DIVISION Files, external resources, I/O assumptions
DATA DIVISION Record definitions, working areas, parameters
PROCEDURE DIVISION The actual processing steps

The especially important parts are these.

  • FILE SECTION Contains the record definitions for input/output files
  • WORKING-STORAGE SECTION Contains everyday variables, flags, counters, work buffers
  • LOCAL-STORAGE SECTION May contain areas re-initialized on each invocation
  • LINKAGE SECTION May contain parameters passed in from outside, the receiving end of a subprogram

If you see a LINKAGE SECTION and PROCEDURE DIVISION USING ..., there is a strong chance the program is not self-contained and runs on data received from outside.

What a LINKAGE SECTION tells youA diagram showing that when a LINKAGE SECTION and a PROCEDURE DIVISION USING are visible, there is a strong chance the program is not self-contained and instead runs on data received from outside.A LINKAGE SECTION is presentLikely runs on data received from outsideA PROCEDURE DIVISION USING is presentNot a self-contained program

Figure 3: Once you see the receiving end defined, read the code assuming a caller exists.

4. Do Not Be Intimidated by the Look of Fixed Format

In old COBOL, the column positions themselves in a source line carry meaning. If you look at it without knowing this, you will never figure out “why is there this weird margin on the left?”1

In fixed format, roughly:

  • Columns 1 - 6: sequence number
  • Column 7: indicator
  • Columns 8 - 11: Area A
  • Columns 12 - 72: Area B

Column 7 is especially important.

  • * or / : comment line
  • - : continuation line
  • D : debugging line
  • *> : a comment that can also appear mid-line

With a ruler above it, the mapping from columns to content looks like this. The first line marks the tens and the second line the ones.

         1         2         3         4         5         6         7         8
12345678901234567890123456789012345678901234567890123456789012345678901234567890
SSSSSSIAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB........
  • S = sequence number (columns 1 - 6)
  • I = indicator (column 7)
  • A = Area A (columns 8 - 11)
  • B = Area B (columns 12 - 72)
  • . = column 73 onward. Some compilers use it as an identification field, but it plays no part in the meaning of the program

Applied to real source, it comes out like this.

000100* This line is a comment because column 7 holds an asterisk
000200 IDENTIFICATION DIVISION.
000300 PROGRAM-ID. SAMPLE01.
000400 DATA DIVISION.
000500 WORKING-STORAGE SECTION.
000600 01  WS-ORDER.
000700     05  WS-ORDER-ID    PIC 9(8).
000800     05  WS-LONG-NAME   PIC X(30) VALUE 'ABCDEFGHIJKLMNOPQRST
000900-    'UVWXYZ0123'.

There are four points to reading it.

  • Columns 1 - 6 are the sequence number. That is what 000100 and friends are in the example above, and they have nothing to do with how the program runs. They are sometimes blank.
  • Column 7 is the indicator. An * means a comment, a - means a continuation line. Line 000900 above is one, continuing the string literal from the previous line.
  • Columns 8 - 11 are Area A. DIVISION, SECTION, paragraph names, FD, and the level numbers 01 and 77 start here. In the example, IDENTIFICATION DIVISION. and 01 WS-ORDER. begin in Area A.
  • Columns 12 - 72 are Area B. Ordinary statements and lower levels such as 05 are written here. In the example, 05 WS-ORDER-ID begins in Area B.

The whitespace here is not “formatting” in the modern sense; parts of it are syntax. Converting tabs in an editor, shifting things left, or copy-pasting carelessly will simply break it. When looking at old source, first question whether the file is fixed format or free format. Run a modern auto-formatter over fixed-format source and the boundary between Area A and Area B collapses, and the code no longer compiles.

Before you touch fixed-format sourceA diagram showing that with old source you first confirm whether it is fixed format or free format, because running a modern auto-formatter or a tab conversion over fixed-format source collapses the boundary between Area A and Area B and the code no longer compiles.Fixed formatFree formatOpened some old sourceFixed or freeColumn positions are syntaxAuto-formatting or tab conversion breaks itColumn rules are loose

Figure 4: Settle the format of the file before you reformat anything.

5. The Bare Minimum of the DATA DIVISION

5.1 Level Numbers

COBOL data definitions build their hierarchy with level numbers, not indentation.2

       01  WS-ORDER.
           05  WS-ORDER-ID    PIC 9(8).
           05  WS-AMOUNT      PIC S9(7)V99 COMP-3.
           05  WS-STATUS      PIC X.
               88  WS-OK      VALUE '0'.
               88  WS-ERROR   VALUE '9'.

       77  WS-COUNT           PIC 9(4).

At minimum, remembering just this much is enough.

  • 01 : top-level record or group forming one unit
  • 02 - 49 : the levels below it
  • 77 : an independent elementary item
  • 88 : condition-name. Attaches a name to a value of the preceding item3
  • 66 : for RENAMES. You will not run into it often, but it exists

What matters is not to think of 88 as a separate bool variable. There is no separate storage area called WS-OK; rather, when WS-STATUS is '0', it can be read under the name WS-OK. That is the feel of it.

What an 88 level really isA diagram showing that an 88 level is not an independent boolean variable but a condition-name attached to values of the preceding item, so WS-OK is simply a name under which WS-STATUS can be read when it holds a particular value.When it is 0When it is 9Base item WS-STATUSWhat is the valueTrue under the name WS-OKTrue under the name WS-ERRORNo separate storage area exists

Figure 5: An 88 is not a variable but a readable alias attached to a value.

One more important thing: it is the level numbers, not the whitespace, that determine the hierarchy. The visual indentation is a useful hint, but what you should ultimately trust is the 01 / 05 / 10 / 88.2

5.2 PICTURE

PIC expresses the shape of an item. The ones you will see most often are these.

Notation Rough meaning
X Character
9 Digit
S Signed
V Decimal point exists only logically
X(10) 10 characters
9(5) 5-digit number
S9(7)V99 Signed, 7 integer digits + 2 decimal digits

For example:

  • PIC X(10) → 10 characters
  • PIC 9(5)V99 → 5 integer digits + 2 decimal digits
  • PIC S9(7)V99 → signed, 7 integer digits + 2 decimal digits

The especially important one here is V. V holds no actual . character. PIC 9(5)V99 is treated as “a number with 2 decimal places,” but there is no dot character in the data. So if you interpret a file or a dump as “the string you see,” you will almost always trip.

V is a logical decimal pointA diagram showing that the V in PIC 9(5)V99 is a logical decimal point that holds no actual dot character, so no dot is stored in the data and interpreting a file or dump as the string you see will trip you up.PIC 9(5)V99Treated as a number with 2 decimal placesNo dot character is stored in the dataReading it as a visible string trips you up

Figure 6: The decimal point lives only in the definition, never in the data.

5.3 USAGE / DISPLAY / COMP / COMP-3

If PIC is the shape, USAGE is the representation in which the item is held. At minimum, grasping just the following gets you a long way.45

Notation Rough meaning Caution when reading
DISPLAY External decimal, visible as characters On a mainframe this may assume EBCDIC6
COMP / BINARY Binary The visible digit count and the internal representation are different things
COMP-3 / PACKED-DECIMAL Packed decimal Looks broken if you read it as characters

For example:

       01  WS-AMOUNT-DISP   PIC S9(7)V99.
       01  WS-AMOUNT-BIN    PIC S9(7) COMP.
       01  WS-AMOUNT-PACK   PIC S9(7)V99 COMP-3.

All three are “numbers,” but they hold their contents differently.

The same number can be held in different waysA diagram showing that even for the same signed numeric shape, DISPLAY is external decimal visible as characters, COMP is binary, and COMP-3 is packed decimal, so the USAGE decides how the contents are held.Numeric items of the same shapeDISPLAY (visible as characters)COMP (binary)COMP-3 (packed decimal)Looks broken when read as characters

Figure 7: Same PIC, different USAGE, and the byte string is a different animal.

What pays off most in practice is your reflex the instant you see COMP-3.

  • That is packed decimal
  • Probably an amount, a tax figure, a count, or a rate
  • It is supposed to look broken when viewed as text
  • Eyeballing it in a CSV or UTF-8 frame of mind will bite you

Holding on to that understanding makes you much less likely to panic needlessly at how dumps and binary files appear.

Your reflex the instant you see COMP-3A diagram showing the reflex to build when you see COMP-3, namely that it is packed decimal, that it is probably an amount or tax figure or count or rate, and that it is supposed to look broken as text so you should not eyeball it in a CSV or UTF-8 frame of mind.Spotted a COMP-3Recognize it as packed decimalSuspect an amount or count fieldStay calm when text looks broken

Figure 8: This one reflex cuts down how often a dump makes you panic.

What a COMP-3 Byte String Actually Looks Like

Work through this by hand once and everything downstream looks different.

Packed decimal has only two rules.45

  1. Pack two decimal digits into each byte
  2. Except that the rightmost byte uses its byte for the last digit plus the sign

The sign is a 4-bit value: C is positive, D is negative, and F is unsigned.

Check it against the examples in IBM’s manual.4

Definition Value Byte string
PIC S9(4) PACKED-DECIMAL +1234 01 23 4C
PIC S9(4) PACKED-DECIMAL -1234 01 23 4D
PIC 9(4) PACKED-DECIMAL 1234 01 23 4F

1234 has 4 digits, so rule 2 leaves one spare digit position at the front. That is the leading 0.

Trace PIC S9(7)V99 COMP-3, which has appeared over and over in this article, the same way.

  • S9(7)V99 is 7 integer digits + 2 decimal digits = 9 digits
  • V only marks the position of the decimal point, so it consumes no bytes at all
  • Packing 9 digits two at a time gives 4 bytes, plus 1 byte for the last digit and the sign. 5 bytes in total

For a value of +12345.67, padding to 9 digits gives 001234567, so it comes out like this.

Value      : +12345.67
9 digits   : 0 0 1 2 3 4 5 6 7  plus the sign
Bytes      : 00 12 34 56 7C
                          ^ sign C = positive

For the negative value -12345.67, only the last byte changes, to 7D.

Bytes      : 00 12 34 56 7D

The real question is what this looks like when you open it as text. Force 00 12 34 56 7C into one ASCII character per byte and you get:

  • 00 is NUL and 12 is a control character, so neither can be displayed as a character at all
  • 34 is 4, 56 is V, and 7C is |

On screen it therefore looks like “a few undisplayable characters followed by 4V|”. The sequence 12345.67 appears nowhere.

This is what “looks like mojibake but is not broken” really is. When you open a dump and meet a meaningless sequence, the first thing to suspect is whether that item’s USAGE is COMP-3.

How a COMP-3 byte string is builtA diagram showing that packed decimal stores two decimal digits per byte and uses the rightmost byte for the last digit plus the sign, so a 9-digit number takes 5 bytes, and that opening those bytes as ASCII never shows the original sequence of digits.Two decimal digits per byteLast byte holds the final digit and the sign9 digits come to 5 bytes in totalOpened as ASCII the original number never appearsLooks like mojibake but is not broken

Figure 9: Learn the two packing rules and a meaningless dump turns into a readable column.

While we are here, a quick lookup table from digit count to byte count. The byte count is “divide the number of 9s by two, drop the fraction, and add one.”

Number of 9s in the PICTURE COMP-3 bytes
1 1
2 - 3 2
4 - 5 3
6 - 7 4
8 - 9 5
10 - 11 6
12 - 13 7

Two digit counts share the same byte count because a spare leading digit position appears only when the number of 9s is even. That is why S9(4) becomes the 3 bytes 01 23 4C and S9(5) also fits in 3 bytes.

Without this table, matching a layout against an external file drifts one byte at a time.

One more note: DISPLAY does not necessarily mean an ASCII string. On z/OS systems EBCDIC is the assumption, so even when digits appear as characters, the byte values can differ from ASCII '0' - '9'.6

5.4 REDEFINES / OCCURS / COPY / FILLER

These four are the places where readers get stuck.

REDEFINES

REDEFINES is a mechanism for viewing the same storage area in a different shape. It is not a copy.7

       01  REC-BUF.
           05  REC-TYPE      PIC X.
           05  REC-DATA      PIC X(99).

       01  HEADER-REC REDEFINES REC-BUF.
           05  HDR-TYPE      PIC X.
           05  HDR-DATE      PIC 9(8).
           05  FILLER        PIC X(91).

This is close to the feel of a union in C-family languages. It often appears in the style of “distinguish one 100-byte area by record type.”

REDEFINES is a second reading of the same areaA diagram showing that REDEFINES is not a copy but a way of viewing the same memory area in a different shape, so a single buffer can be read as a generic record or as a header record, and writing through one view changes how the other view looks.One memory areaViewed as REC-BUFViewed as HEADER-RECWrite through one and both views change

Figure 10: Two definitions, but only one byte string underneath.

OCCURS

OCCURS is an array. In COBOL it tends to be called a table.

       05  WS-ITEM OCCURS 12 TIMES.
           10  WS-PRICE    PIC 9(5).

If you further encounter OCCURS DEPENDING ON, it is a variable-length table. In that case it can affect the positions of the items that follow, so following it with a fixed-length mindset will make you lose your footing.8

What to watch for with OCCURS DEPENDING ONA diagram showing that OCCURS is an array and that adding DEPENDING ON makes it a variable-length table whose value can move the positions of the items that follow, so chasing offsets with a fixed-length mindset makes you lose your footing.Not attachedAttachedSpotted an OCCURSIs DEPENDING ON attachedTable with a fixed countVariable-length tablePositions of later items can move

Figure 11: Three extra words, DEPENDING ON, change the premise of every offset calculation.

COPY

COPY is a compile-time include. In other words, the source you have open may not be the finished form yet.9

       COPY CUSTOMER-REC.
       COPY ERROR-MAP.

It is entirely normal for record definitions, shared flags, host variables for SQL, and external interfaces to be stuffed into copybooks.

When heavy use of COPY makes the source hard to read, it is faster to check whether you can get at the expanded source or a compiler listing. IBM Enterprise COBOL even has an option called MDECK for writing out the input source after library processing.10

How to read source that uses COPYA diagram showing that COPY is a compile-time include so the source you have open may not be the finished form, which means you open the copybooks to check, and when it is hard to read you look for the expanded source, a compiler listing, or the output of the MDECK option.When it is hard to readSpotted a COPYThe open source may be incompleteOpen the copybook and checkLook for expanded source or a listing

Figure 12: The lines in front of you are not necessarily all of the program.

FILLER

FILLER is an item with no name. But “unreferenced, therefore meaningless” is wrong.

It routinely serves as:

  • Reserved space
  • A compatibility gap for an old specification
  • Padding to match a record length
  • Slack for a REDEFINES

FILLER merely lacks a name; it still exists as bytes. Forget this and your mapping against an external file drifts out of alignment one byte at a time.

What happens when you forget to count FILLERA diagram showing that FILLER merely lacks a name while still existing as bytes and serving as reserved space or record-length padding, so forgetting to count it makes the mapping against an external file drift one byte at a time.IncludedForgottenFILLER is an item with no nameIt still exists as bytesIncluded in the layout calculationMatches the external fileDrifts one byte at a time

Figure 13: Even an item nobody references still has a job: its length.

6. The Bare Minimum of the PROCEDURE DIVISION

If the DATA DIVISION is the map, the PROCEDURE DIVISION is the route you travel.

6.1 PERFORM

PERFORM is COBOL’s basic control transfer. Roughly speaking, it means call a piece of processing and come back.11

The forms you will see most often are these.

       PERFORM INIT-PROC
       PERFORM UNTIL EOF
           PERFORM READ-PROC
           IF NOT EOF
               PERFORM EDIT-PROC
               PERFORM WRITE-PROC
           END-IF
       END-PERFORM

PERFORM comes in two broad flavors.

  • Out-of-line PERFORM, which names a paragraph or section
  • Inline PERFORM ... END-PERFORM, which writes a block in place

In older code you will also routinely see range forms like PERFORM A-100 THRU A-199. Convenient, but adding a paragraph in the middle can silently pull that paragraph into the range, so when reading, check carefully where the range ends.

The three forms of PERFORMA diagram showing that PERFORM comes as an out-of-line form that calls a paragraph or section and returns, an inline form that writes a block in place, and a THRU range form found in older code where adding a paragraph in the middle can silently pull that paragraph into the range.Spotted a PERFORMOut-of-line, calling a paragraphInline, written in placeTHRU range formAlways check where the range ends

Figure 14: The form of the PERFORM decides which return point and which range you have to follow.

6.2 IF / EVALUATE / Scope

For conditional branching, IF is the basic tool. Thinking of EVALUATE as roughly a switch/case is mostly correct.

What you need to watch is how scopes end.12

Code with explicit terminators such as

  • END-IF
  • END-PERFORM
  • END-READ

is still the readable kind.

The problem is old code. In COBOL, . acts as an implicit scope terminator and closes all the still-open statements at once.12

That means a single period changes:

  • How far the IF extends
  • How far the PERFORM extends
  • Where the next sentence begins

Furthermore, NEXT SENTENCE is not the same as CONTINUE. NEXT SENTENCE jumps to the point after the next period, so its destination shifts depending on where the following . happens to be.12

When reading old COBOL, “watch the periods, not the line endings” is about the right calibration.

How much a period weighsA diagram showing that code with explicit terminators such as END-IF is easy to read, while in old code the period acts as an implicit scope terminator that closes every still-open statement at once, so a single period changes how far an IF or a PERFORM extends.END-IF and friends presentAbsent, old codeAre there explicit terminatorsThe ranges are easy to readThe period is the implicit terminatorOne position changes the rangeWatch the periods, not the line endings

Figure 15: In old code, the control flow is held by where the periods sit.

6.3 READ / WRITE / CALL

The frequent fliers in business COBOL are these.

  • READ
  • WRITE
  • REWRITE
  • START
  • CALL

READ ... AT END ... in particular is the classic pattern.

       READ IN-FILE
           AT END
               SET EOF TO TRUE
           NOT AT END
               PERFORM PROCESS-REC
       END-READ

If there is a CALL 'SUBPGM' USING ..., control jumps to another program. In that case, look at the callee’s LINKAGE SECTION and PROCEDURE DIVISION USING, and the shape of the handoff becomes quite visible.

How to follow a CALL you have foundA diagram showing that a CALL transfers control to another program, so looking at the callee's LINKAGE SECTION and PROCEDURE DIVISION USING reveals the shape of the argument handoff.Spotted a CALLControl jumps to another programLook at the callee's LINKAGE SECTIONRead the handoff shape from USING

Figure 16: A call only makes sense together with the receiving end on the other side.

7. What Lives Outside COBOL

Quite often, COBOL’s world is not self-contained in the source.

  • File definitions
  • The execution environment
  • DB connections
  • The transaction environment
  • Job control

all live outside it.

At minimum, grasping the following makes reading much easier.

Files and FILE STATUS

Read the FILE-CONTROL in the ENVIRONMENT DIVISION together with the FILE SECTION / FD in the DATA DIVISION. They come as a pair.13

       SELECT IN-FILE ASSIGN TO ...
           FILE STATUS IS WS-FS.

       FD  IN-FILE.
       01  IN-REC.
           05 ...

If there is a FILE STATUS, it receives the result code after each I/O. When reading file-related failures or EOF handling, you cannot even begin without looking at this.14

Read the file definition as a pairA diagram showing that the SELECT in the FILE-CONTROL of the ENVIRONMENT DIVISION and the FD in the DATA DIVISION are read as a pair, and that when a FILE STATUS is present it receives the result code after each I/O, which is where reading file failures and EOF handling starts.SELECT in FILE-CONTROLOne file definition, taken togetherFD in the FILE SECTIONFILE STATUS receives the result codeStarting point for failures and EOF handling

Figure 17: The full picture of a file is written across two DIVISIONs.

EXEC SQL

If this appears, it is embedded SQL.

       EXEC SQL
           SELECT ...
       END-EXEC.

In this case the COBOL is a “vessel for host variables,” and the actual selection criteria and update targets are on the SQL side. So the shortcut is to read the contents of EXEC SQL as ordinary SQL.

EXEC CICS

If this appears, you are in a CICS transaction context.15

       EXEC CICS
           RECEIVE MAP(...)
       END-EXEC.

At that instant, this stops being a plain batch-reading exercise. You need to read it together with the external context: screens, transactions, response codes, the COMMAREA, and so on.

JCL and Execution Definitions

In mainframe batch, it is not unusual for which datasets actually get allocated and in what order the jobs flow to live outside the COBOL source. When you look at the source alone and cannot tell “where is this file?”, it routinely turns out that the code is not at fault; you just have not widened your view far enough yet.

The world that sits outside the sourceA diagram showing that when EXEC SQL appears the real selection criteria live on the SQL side, when EXEC CICS appears the screen and transaction context lives outside, and in mainframe batch the dataset allocation and job order live in the JCL, so parts of the world sit outside the COBOL source.EXEC SQLLook outside the source tooEXEC CICSJCL and execution definitionsSQL criteria, screen context, job flow

Figure 18: Sometimes the confusion is not the code’s fault; your view is just too narrow.

Differences Between Compilers

This article is written with IBM systems in mind, but in practice you will also meet Micro Focus and COBOL on Linux or Windows. The skeleton is shared, so the way you read does not change, yet the points where “the same code produces a different result” are well known, and knowing them in advance keeps you out of trouble.

What to check z/OS family (IBM Enterprise COBOL) Open systems (Micro Focus, Linux / Windows editions, and so on)
Character encoding EBCDIC is the assumption6 ASCII is the assumption6
Reference format Fixed format is the traditional default. Free format is also available1 Both fixed and free exist, and which one is the default depends on the build settings1
How copybooks are found Library specification Search paths given as compiler options
Dialects An option exists for switching which compiler to match

The one that bites hardest is character encoding. Even with the same PIC X(10), reading a file written on z/OS directly on Windows gives you different byte values even for digits. Most cases of “everything turned to garbage after the transfer” are this, and it is a separate problem from the COMP-3 story.

There is one more place where internal numeric representation tends to differ. In IBM Enterprise COBOL, BINARY / COMP-4 truncates at the digit count written in the PICTURE, whereas COMP-5 holds values up to the capacity of a native binary field of 2, 4, or 8 bytes, with truncation happening at the binary size instead.16 In other words, PIC S9(4) COMP and PIC S9(4) COMP-5 look alike but accept different maximum values. When COMP-5 turns up in code that exchanges values with other systems in binary form, read it as written that way on purpose.

The shortest path for checking all this against your own environment is:

  1. Open the build definition first (makefile, JCL, project settings). Which compiler and which options are in use become clear before you even read the source.
  2. Settle the reference format (fixed or free). Get this wrong and formatting in an editor will break the code.
  3. Settle the character encoding. EBCDIC or ASCII changes how you read a dump.
  4. Mark the COMP-family items. This is where compiler differences show up almost every time.
The shortest path for checking your own environmentA diagram showing the steps of opening the build definition first to see which compiler and options are used, settling whether the reference format is fixed or free, settling whether the character encoding is EBCDIC or ASCII, and marking the COMP-family items where compiler differences appear.Open the build definition firstSettle the reference formatSettle the character encodingMark the COMP-family items

Figure 19: Four moves before you dive into the source keep compiler differences from biting.

8. The Minimum Reading Order

When you suddenly have to read COBOL, the following order is the safe one.

  1. Sweep up every COPY Open the copybooks if you can. If not, look for a listing or the expanded source
  2. Pick out the 01-level record definitions List the top-level items in the FILE SECTION, WORKING-STORAGE, and LINKAGE SECTION
  3. Read the PICs and USAGEs Identify amounts, dates, counts, codes, flags
  4. Search for READ / WRITE / REWRITE / CALL / EXEC SQL / EXEC CICS Grasp the I/O and the external boundaries first
  5. Follow only the first main path Trace the chain of PERFORMs from the top of the PROCEDURE DIVISION
  6. Look at the 88s and status items The meanings of EOF, success/failure, and type codes become much easier to read
  7. Mark every REDEFINES / OCCURS DEPENDING ON / COMP-3 They will matter later without fail, so flag them as hazardous material up front
  8. For files, look at the FILE STATUS This eliminates a lot of misreadings around I/O errors

In this order, you avoid having to close-read the whole thing from the start. With COBOL, rather than trying to understand 100% from the beginning, it is far easier to nail down the three points, records, external boundaries, main path, and then go into the details.

A safe reading orderA diagram showing a reading order that sweeps up every COPY to check the copybooks, lists the 01-level record definitions, reads the shape of items from PIC and USAGE, searches out the I/O and external boundaries, follows the chain of PERFORMs from the top of the PROCEDURE DIVISION along the main path, and marks the hazardous items.Sweep up every COPYList the 01 levelsRead the shapes from PIC and USAGESearch for I/O and external boundariesFollow the main path of PERFORMsMark the hazardous items

Figure 20: Not a full close read, but filling in the outer moat in this order.

8.1 Exercise: Try Reading This Record Definition

Explanation alone does not stick, so here is one small record. Work out your own answers first, then look at the solutions below.

       01  CUST-REC.
           05  CUST-ID          PIC X(8).
           05  CUST-NAME        PIC X(20).
           05  CUST-KBN         PIC X.
               88  CUST-NORMAL  VALUE '0'.
               88  CUST-VIP     VALUE '1'.
           05  CUST-BALANCE     PIC S9(7)V99 COMP-3.
           05  CUST-HIST OCCURS 3 TIMES.
               10  HIST-DATE    PIC 9(8).
               10  HIST-AMOUNT  PIC S9(5)V99 COMP-3.
           05  FILLER           PIC X(4).

Questions

  1. How many bytes is CUST-REC in total?
  2. Counting from the start of the record, at which byte does CUST-BALANCE begin?
  3. At which byte does the second HIST-AMOUNT begin?
  4. When CUST-KBN holds '1', which condition-name is true?
  5. When you open this file in a text editor, which items look broken?
  6. Name two things this definition alone does not tell you.

Answers

  1. 74 bytes. The breakdown is:

    Item Calculation Bytes
    CUST-ID X(8) 8
    CUST-NAME X(20) 20
    CUST-KBN X 1
    CUST-BALANCE 9-digit COMP-3 5
    CUST-HIST (8 + 4) × 3 occurrences 36
    FILLER X(4) 4
    Total   74

    An 88 level is a condition-name, so it consumes no bytes. Counting it is a common mistake. FILLER merely lacks a name; its 4 bytes are very much there.

  2. Byte 30. There are 8 + 20 + 1 = 29 bytes ahead of it, so it starts right after them.

  3. Byte 55. CUST-HIST starts at byte 35 and each occurrence is 12 bytes, so the first occurrence spans bytes 35 - 46 and the second spans bytes 47 - 58. The first 8 bytes of that are HIST-DATE, so HIST-AMOUNT starts at byte 55.

  4. CUST-VIP. There is no separate area called CUST-VIP; it is simply the name under which CUST-KBN can be read when it holds '1'.

  5. CUST-BALANCE and HIST-AMOUNT. Both are COMP-3, so reading them as characters yields a meaningless sequence. HIST-DATE is PIC 9(8) in DISPLAY, so in an ASCII environment it reads as digits, such as 20260317. In an EBCDIC environment, though, the byte values differ from ASCII even when the digits look right.

  6. For example:

    • Whether this definition itself was brought in via COPY. Without looking at the copybook side, you cannot tell whether this is the version actually in use.
    • Whether the file’s character encoding is EBCDIC or ASCII. It changes how PIC X and PIC 9 DISPLAY look.
    • Whether operations ever put something other than '0' or '1' into CUST-KBN. Only two condition-names are defined, but that is no guarantee no other value arrives.
    • The attributes of the file itself (record length, fixed or variable length, FILE STATUS). You cannot know these without looking at the ENVIRONMENT DIVISION and the FD.

If you got questions 1 and 3 wrong, go back to the digit-count and byte-count table in 5.3. Misreadings of COBOL usually start right there.

9. Common Stumbling Points

Finally, here are the places where beginners get caught with very high probability.

Thinking REDEFINES is “a different variable”

It is not. It is the same storage area read in a different shape. Modify one side, and the other side’s view changes too.7

Thinking 88 is “an independent bool”

It is not. It is just a name attached to a value of the preceding item. Behind the scenes, SET WS-OK TO TRUE stores the corresponding value into the underlying item.3

Ignoring COPY and reading only the main body

The file you have open is still only half the whole. It is entirely normal for field definitions, shared flags, and host variables to live wholesale outside it.9

Thinking MOVE is plain assignment

MOVE is not just a memcpy. Depending on the receiving item’s type, it can involve conversion, digit alignment, zero filling, truncation, and editing or de-editing.17

Underestimating the effect of .

COBOL’s . is heavier than you imagine. In old code with no explicit terminators, misjudging how much this period closes means misreading the control flow.12

Thinking packed decimal or EBCDIC is “mojibake”

It is not necessarily broken. Quite often it simply was never a string to begin with, or is just not ASCII.46

Assuming what follows OCCURS DEPENDING ON sits at a fixed position

The items after a variable-length table can move position depending on the value. Read it with a fixed-length mindset and all your offset calculations go wrong.8

10. Quick Reference: What to Think First

Word you found First thing to think
01 Top level of a record or group. Grasp the big picture from here
88 Named meaning of a flag or status code. The key to reading branches
PIC X(...) Character item
PIC 9(...) / S9(...)V... Numeric item. Check digit count and decimal position
COMP Binary
COMP-3 Packed decimal. Likely an amount or a count
REDEFINES The same area being reinterpreted differently
OCCURS Array / table
OCCURS DEPENDING ON Variable length. Watch the positions that follow too
FILLER No name, but it has length
COPY You cannot see the finished form without the copybook
PERFORM The skeleton of the main path
READ / WRITE / REWRITE File I/O
EXEC SQL DB processing
EXEC CICS Transaction processing
FILE STATUS I/O result code

11. Summary

COBOL is not hard because it is old. It is just that data definitions, external files, and the execution context are tightly intertwined, which makes the initial entry point hard to see.

To restate the minimum set for reading it:

  • Grasp the map via the DIVISIONs
  • Read the DATA DIVISION first
  • Read the shape of each item via PIC and USAGE
  • Mark every COMP-3, REDEFINES, OCCURS, 88, and COPY
  • Follow PERFORM, READ, WRITE, and CALL
  • Nail down the external boundaries via FILE STATUS, EXEC SQL, and EXEC CICS
  • Do not underestimate how . behaves

Once you can see all this, COBOL turns from “mysterious ancient magic” into “a record-processing language.” Legacy technology is not scary because the name is old; it is just that picking the wrong scale for your first look suddenly makes it hard to understand. Get the map scale right, and it reads surprisingly normally.

Get the scale right and readA diagram showing that grasping the map from the DIVISIONs, reading the DATA DIVISION first to pin down the shape of each item, following the flow through PERFORM and the I/O statements, and nailing down the external boundaries with FILE STATUS, EXEC SQL, and EXEC CICS sets the scale so that COBOL reads as an ordinary record-processing language.Grasp the map from the DIVISIONsRead the shapes in the DATA DIVISIONFollow the flow via PERFORM and I/ONail down the external boundariesAncient magic becomes a record-processing language

Figure 21: The difficulty is not the age of the language but the scale you pick first.

12. References

The main sources referenced in the text. The superscript numbers in the body link straight into this list, and the arrow at the end of each entry takes you back to where it was cited.

Recent articles sharing the same tags. Deepen your understanding with closely related topics.

These topic pages place the article in a broader service and decision context.

This article connects naturally to the following service pages.

Frequently Asked Questions

Common questions about the topic of this article.

Where should I start when reading COBOL source code?
Reading only the PROCEDURE DIVISION gives you half the story. Before COBOL is a language of logic it is very much a language of record definitions, so look at the DATA DIVISION first. A safe reading order is: sweep up every COPY and check the copybooks, list the 01-level record definitions, read the shape of each item from its PIC and USAGE, search for READ, WRITE, CALL, EXEC SQL, and EXEC CICS to grasp the I/O and the external boundaries, and only then follow the chain of PERFORMs from the top of the PROCEDURE DIVISION along the main path alone.
What does PIC S9(7)V99 COMP-3 mean?
PIC describes the shape of an item and USAGE describes the representation it is held in. S9(7)V99 is a signed number with 7 integer digits plus 2 decimal digits, but the V is a logical decimal point only: no dot character is stored in the data. COMP-3 is packed decimal, and it turns up constantly for amounts, tax figures, counts, and rates. Such a field is supposed to look broken when viewed as text, so eyeballing a dump in a CSV or UTF-8 frame of mind will trip you up.
How should I understand COBOL's 88 levels and REDEFINES?
An 88 is not an independent boolean variable. It is a condition-name that attaches a name to values of the preceding item. SET WS-OK TO TRUE stores the corresponding value into the underlying item behind the scenes. REDEFINES is a mechanism for viewing the same memory area in a different shape; it is not a copy, and the feel is closer to a union in the C family. Writing through one view changes how the other view looks, which is why it shows up so often as a way of distinguishing record types within a single area.
What should I do when there are so many COPY statements that I cannot see the whole program?
COPY is a compile-time include, so the source you have open may not be the finished form yet. It is entirely normal for record definitions, shared flags, host variables for SQL, and external interfaces to be pushed into copybooks. When it gets hard to read, the fast move is to check whether you can get at the expanded source or a compiler listing; IBM Enterprise COBOL also has an option called MDECK that writes out the input source after library processing.

Author Profile

Profile page for the article author.

Go Komura

Representative of KomuraSoft LLC

Focused on Windows software development, technical consulting, and investigations into failures that are difficult to reproduce.

Back to the Blog