Legacy Features and Migration

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This page is written against ISO/IEC 1989:2023 (edition 3, January 2023; an upcoming 4th edition is in ballot), in which the obsolete elements of COBOL 85 are gone; against IBM Enterprise COBOL for z/OS 6.5 (PTFs through September 2026) on z/OS 3.2, which still accepts most of them; and against GnuCOBOL 3.2 (28 July 2023, the latest release), with which every program on it was compiled with cobc -x -Wall and run on 2026-10-20 (and again with -std=ibm where it differs). z/OS facts are labelled "z/OS; not run here" and checked against the Language Reference and Programming Guide.

This is the only page in the guide that shows obsolete elements. Every other page uses the structured form. Each item below is labelled old code you may meet, then shows the modern form next to it: a maintenance programmer needs to read such code and to replace it safely, not to write it. The pages that teach the modern forms are PERFORM and Structured Programming, Conditions, IF and EVALUATE, STRING, UNSTRING and INSPECT and Dates and Times; the strategy for deciding what to do with a legacy estate is on Modernization Strategies.

Where the book is outdated: Taulli’s Modern Mainframe Development (2022, chapter 4) presents GO TO alongside the other control-flow forms and its migration discussion (chapters 12 and 13) stops at moving to the cloud. It does not say that the 85 standard already classed ALTER and GO TO without a target as obsolete, that the 2002 standard removed them, that Enterprise COBOL 6 no longer behaves like COBOL 4 on invalid data, or that the Enterprise COBOL 6 compiler changed the code it generates. All three are covered below.

How the compilers flag obsolete elements

IBM’s Language Reference lists the elements that the 85 COBOL standard categorised as obsolete. The 2002 standard does not contain them, and the manual says that this does not imply IBM will remove them from a future release. The list for Enterprise COBOL for z/OS 6.5 is:

  • the ALTER statement and GO TO without a procedure-name;

  • the AUTHOR, INSTALLATION, DATE-WRITTEN, DATE-COMPILED and SECURITY paragraphs and the comment entry;

  • the DATA RECORDS, LABEL RECORDS, MEMORY SIZE, MULTIPLE FILE TAPE, RERUN and VALUE OF clauses and the REVERSED phrase;

  • the DEBUG-ITEM special register, debugging sections and the USE FOR DEBUGGING declarative;

  • the ENTER statement, the STOP literal format of STOP, and the segmentation module;

  • the figurative constant ALL literal with a length greater than one, when it is associated with a numeric or numeric-edited item.

EXAMINE, TRANSFORM, NOTE, ON and REMARKS do not appear in that list or anywhere in the 6.5 Language Reference and Programming Guide: they belong to the older OS/VS COBOL language, which the 85 standard (and every IBM compiler since VS COBOL II) did not carry forward, so a program that still uses them must be changed before it can move to Enterprise COBOL 6. The 6.5 Programming Guide also shows NOCMPR2 as "always off" in the object-module signature table: the compatibility option that once let VS COBOL II compile OS/VS COBOL programs no longer exists.

GnuCOBOL reports the 85-obsolete items itself. -Wall turns on -Wobsolete and -Warchaic, and each diagnostic names the dialect (obsolete in GnuCOBOL, obsolete in IBM COBOL (lax)), so the same source gives different messages under -std=ibm. Elements GnuCOBOL does not know are errors, with a note that the word "may exist in another dialect".

Old code you may meet

Identification paragraphs, LABEL RECORDS, DATA RECORDS (old code you may meet)

Before the 85 standard, documentation lived inside the program and files carried label information:

       IDENTIFICATION DIVISION.
       PROGRAM-ID. OLD1.
       AUTHOR. CIRCULATION TEAM.
       INSTALLATION. MAIN LIBRARY.
       DATE-WRITTEN. 1987-03-02.
       ENVIRONMENT DIVISION.
       INPUT-OUTPUT SECTION.
       FILE-CONTROL.
           SELECT LOANTRAN ASSIGN TO "loantran.dat"
               ORGANIZATION IS LINE SEQUENTIAL.
       DATA DIVISION.
       FILE SECTION.
       FD  LOANTRAN
           LABEL RECORDS ARE STANDARD
           DATA RECORD IS LOAN-LINE.
       01  LOAN-LINE            PIC X(20).
       WORKING-STORAGE SECTION.
       77  SWITCH-1             PIC 9 VALUE 0.
       PROCEDURE DIVISION.
       MAIN-PARA.
           DISPLAY "OLD1 STARTED".
           STOP RUN.

Compiled with cobc -x -Wall old1.cob it prints one warning per element, and with -std=ibm it still compiles and runs (the dialect is lax about them):

old1.cob:3: warning: AUTHOR is obsolete in GnuCOBOL [-Wobsolete]
old1.cob:4: warning: INSTALLATION is obsolete in GnuCOBOL [-Wobsolete]
old1.cob:5: warning: DATE-WRITTEN is obsolete in GnuCOBOL [-Wobsolete]
old1.cob:14: warning: LABEL RECORDS is obsolete in GnuCOBOL [-Wobsolete]
old1.cob:15: warning: DATA RECORDS is obsolete in GnuCOBOL [-Wobsolete]

(Each real message is followed by a few lines of source context; they are omitted here.) The same program with -std=ibm -Wall reports LABEL RECORDS is obsolete in IBM COBOL (lax) and DATA RECORDS is obsolete in IBM COBOL (lax) and no more, because IBM accepts the identification paragraphs. A related OS/VS item, REMARKS, and the MEMORY SIZE clause of OBJECT-COMPUTER, give REMARKS is obsolete in GnuCOBOL and MEMORY SIZE is obsolete in GnuCOBOL.

The modern form: delete LABEL RECORDS and DATA RECORDS (the system handles labels, the record name is the 01 under the FD), and replace the paragraphs with comments, or better with the source-control history and the file header of the repository. In free format a comment is *>; in fixed format it is a * in column 7.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. OLD1M.
      * Circulation loan listing. Owner and history: see the
      * repository, not this header.
       ENVIRONMENT DIVISION.
       INPUT-OUTPUT SECTION.
       FILE-CONTROL.
           SELECT LOANTRAN ASSIGN TO "loantran.dat"
               ORGANIZATION IS LINE SEQUENTIAL.
       DATA DIVISION.
       FILE SECTION.
       FD  LOANTRAN.
       01  LOAN-LINE            PIC X(20).
       WORKING-STORAGE SECTION.
       PROCEDURE DIVISION.
       MAIN-PARA.
           DISPLAY "OLD1M STARTED"
           STOP RUN.

ALTER and GO TO (old code you may meet)

ALTER changes, while the program runs, the target of a GO TO in another paragraph, which must contain nothing else. The program text no longer says where control goes: you have to know which ALTER ran last.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. ALTER1.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  PASS-NO              PIC 9 VALUE 0.
       PROCEDURE DIVISION.
       MAIN-PARA.
           PERFORM RUN-ONCE 2 TIMES.
           STOP RUN.
       RUN-ONCE.
           ADD 1 TO PASS-NO.
           PERFORM SWITCH-PARA THRU SECOND-STEP.
       SWITCH-PARA.
           GO TO FIRST-STEP.
       FIRST-STEP.
           DISPLAY "PASS " PASS-NO " TOOK THE FIRST BRANCH".
           ALTER SWITCH-PARA TO PROCEED TO SECOND-STEP.
       SECOND-STEP.
           DISPLAY "PASS " PASS-NO " REACHED SECOND-STEP".

Compiled with cobc -x -Wall alter1.cob it prints one warning, and with -std=ibm the same warning reads ALTER is obsolete in IBM COBOL (lax):

alter1.cob: in paragraph 'FIRST-STEP':
alter1.cob:17: warning: ALTER is obsolete in GnuCOBOL [-Wobsolete]
   15 |        FIRST-STEP.
   16 |            DISPLAY "PASS " PASS-NO " TOOK THE FIRST BRANCH".
   17 >            ALTER SWITCH-PARA TO PROCEED TO SECOND-STEP.
   18 |        SECOND-STEP.
   19 |            DISPLAY "PASS " PASS-NO " REACHED SECOND-STEP".

and when run, it prints:

PASS 1 TOOK THE FIRST BRANCH
PASS 1 REACHED SECOND-STEP
PASS 2 REACHED SECOND-STEP

On the second pass SWITCH-PARA jumps straight to SECOND-STEP, because the first pass changed it: the behaviour of the paragraph depends on the history of the run, and a program that is called again starts in the state the previous call left. The modern form is a switch, ideally with level 88 names (see VALUE, INITIALIZE and Condition Names):

       IDENTIFICATION DIVISION.
       PROGRAM-ID. MODERN1.
      * Circulation: the same two jobs as ALTER1 and GOTO1,
      * written with structured statements only.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  PASS-NO              PIC 9 VALUE 0.
       01  BRANCH-FLAG          PIC X VALUE "F".
           88  FIRST-BRANCH           VALUE "F".
           88  SECOND-BRANCH          VALUE "S".
       01  TRAN-CODE            PIC 9 VALUE 2.
       01  A                    PIC 9 VALUE 1.
       01  B                    PIC 9 VALUE 0.
       PROCEDURE DIVISION.
       MAIN-PARA.
           PERFORM RUN-ONCE 2 TIMES
           PERFORM DISPATCH-PARA
           PERFORM NESTED-IF-PARA
           STOP RUN.
       RUN-ONCE.
           ADD 1 TO PASS-NO
           IF FIRST-BRANCH
               DISPLAY "PASS " PASS-NO " TOOK THE FIRST BRANCH"
               SET SECOND-BRANCH TO TRUE
           END-IF
           DISPLAY "PASS " PASS-NO " REACHED SECOND-STEP".
       DISPATCH-PARA.
           EVALUATE TRAN-CODE
               WHEN 1 DISPLAY "LOAN"
               WHEN 2 DISPLAY "RETURN"
               WHEN 3 DISPLAY "RENEW"
               WHEN OTHER DISPLAY "UNKNOWN CODE"
           END-EVALUATE.
       NESTED-IF-PARA.
           IF A = 1
               IF B = 1
                   DISPLAY "A AND B"
               ELSE
                   CONTINUE
               END-IF
               DISPLAY "STILL IN THE IF"
           END-IF
           DISPLAY "AFTER THE IF".

Compiled with cobc -x -Wall modern1.cob (and with -std=ibm), no warnings, it prints the same two passes, then the results of the next two sections:

PASS 1 TOOK THE FIRST BRANCH
PASS 1 REACHED SECOND-STEP
PASS 2 REACHED SECOND-STEP
RETURN
STILL IN THE IF
AFTER THE IF

GO TO without a procedure-name is the other half of the pair: GO TO. was written in a paragraph that an ALTER filled in later. If you find one, there is an ALTER somewhere. Replace both together.

How to remove ALTER safely. Find every ALTER and the paragraph it names; list the possible targets of each; write a test that drives each target (see Testing COBOL Programs); replace the GO TO and the ALTER by an IF or EVALUATE on a switch; run the tests again. Do not combine this with other changes.

GO TO DEPENDING ON and PERFORM THRU (old code you may meet)

GO TO DEPENDING ON is a computed jump. It is still valid in the 2002 and 2023 standards, so no compiler warns about it, but it is the ancestor of EVALUATE and is replaced by it:

       IDENTIFICATION DIVISION.
       PROGRAM-ID. GOTO1.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  TRAN-CODE            PIC 9 VALUE 2.
       PROCEDURE DIVISION.
       MAIN-PARA.
           GO TO LOAN-PARA RETURN-PARA RENEW-PARA
               DEPENDING ON TRAN-CODE.
           DISPLAY "UNKNOWN CODE".
           STOP RUN.
       LOAN-PARA.
           DISPLAY "LOAN".
           STOP RUN.
       RETURN-PARA.
           DISPLAY "RETURN".
           STOP RUN.
       RENEW-PARA.
           DISPLAY "RENEW".
           STOP RUN.

Compiled with cobc -x -Wall goto1.cob, no warnings, it prints:

RETURN

The jump uses the number in TRAN-CODE; a value outside 1 to 3 falls through to the next statement ("UNKNOWN CODE"), which is the behaviour to keep. The EVALUATE in DISPATCH-PARA in the modern program above does the same with WHEN OTHER. Similarly, PERFORM THRU over a range of paragraphs makes the end of the range a hidden contract: adding a paragraph in the middle changes what runs. Prefer PERFORM of one paragraph or of a SECTION. When you meet a THRU range, find the exit paragraph (usually named ...-EXIT. and containing only EXIT.) and every GO TO that targets it before touching anything.

NEXT SENTENCE (old code you may meet)

NEXT SENTENCE jumps to the statement after the next full stop. Inside a scope-terminated IF that is not the end of the IF: it can skip statements that look as if they belong to it.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. NEXTSENT1.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  A                    PIC 9 VALUE 1.
       01  B                    PIC 9 VALUE 0.
       PROCEDURE DIVISION.
       MAIN-PARA.
           IF A = 1
               IF B = 1
                   DISPLAY "A AND B"
               ELSE
                   NEXT SENTENCE
               END-IF
               DISPLAY "STILL IN THE IF"
           END-IF
           DISPLAY "AFTER THE IF".
           IF A = 1 NEXT SENTENCE ELSE DISPLAY "NOT ONE".
           DISPLAY "NEXT SENTENCE LANDED HERE".
           STOP RUN.

Compiled with cobc -x -Wall nextsent1.cob it warns twice and, when run, prints one line:

nextsent1.cob: in paragraph 'MAIN-PARA':
nextsent1.cob:13: warning: NEXT SENTENCE is archaic in GnuCOBOL [-Warchaic]
nextsent1.cob:18: warning: NEXT SENTENCE is archaic in GnuCOBOL [-Warchaic]
NEXT SENTENCE LANDED HERE

(The real messages carry source context.) Neither "STILL IN THE IF" nor "AFTER THE IF" was shown, because the first NEXT SENTENCE skipped to the full stop after AFTER THE IF. In the modern program the same shape uses CONTINUE, and both lines are printed. Note that the IBM Language Reference says explicitly that NEXT SENTENCE combined with END-IF does not pass control to the statement following END-IF, and it lists the combination among the things the 85 standard disallows. It remains a style hazard, not a removed feature: replace it by CONTINUE when you restructure.

EXAMINE and TRANSFORM (old code you may meet)

These OS/VS COBOL statements count, replace and translate characters. INSPECT replaced both (EXAMINE ... TALLYING is INSPECT ... TALLYING, EXAMINE ... REPLACING is INSPECT ... REPLACING, TRANSFORM is INSPECT ... CONVERTING).

       IDENTIFICATION DIVISION.
       PROGRAM-ID. EXAM1.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  TITLE-TEXT           PIC X(8) VALUE "TOO COOL".
       PROCEDURE DIVISION.
       MAIN-PARA.
           EXAMINE TITLE-TEXT REPLACING ALL "O" BY "0".
           DISPLAY TITLE-TEXT.
           STOP RUN.

The default GnuCOBOL dialect does not know the word, so cobc -x -Wall exam1.cob stops:

exam1.cob: in paragraph 'MAIN-PARA':
exam1.cob:8: error: syntax error, unexpected Identifier, expecting SECTION or .
    6 |        PROCEDURE DIVISION.
    7 |        MAIN-PARA.
    8 >            EXAMINE TITLE-TEXT REPLACING ALL "O" BY "0".
    9 |            DISPLAY TITLE-TEXT.
   10 |            STOP RUN.
exam1.cob:8: note: unknown statement 'EXAMINE'; it may exist in another dialect

Compiled with -std=ibm or -std=mvs, the same file compiles and prints T00 C00L: those dialects make EXAMINE a reserved word and accept it. TRANSFORM TITLE-TEXT FROM "O" TO "0" compiles in the default dialect too and prints the same text. Do not rely on that on z/OS: the Enterprise COBOL 6.5 manuals do not describe either statement, so assume an IBM compile will reject them. The modern program:

       IDENTIFICATION DIVISION.
       PROGRAM-ID. INSPECT1.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  TITLE-TEXT           PIC X(8) VALUE "TOO COOL".
       01  O-COUNT              PIC 9 VALUE 0.
       PROCEDURE DIVISION.
       MAIN-PARA.
           INSPECT TITLE-TEXT TALLYING O-COUNT FOR ALL "O".
           INSPECT TITLE-TEXT REPLACING ALL "O" BY "0".
           INSPECT TITLE-TEXT CONVERTING "OL" TO "01".
           DISPLAY O-COUNT " " TITLE-TEXT.
           STOP RUN.

Compiled with cobc -x -Wall inspect1.cob (and with -std=ibm), no warnings, it prints:

4 T00 C001

The four is the count of capital O in TOO COOL (TALLYING runs first); REPLACING changes them to zero digits, and CONVERTING "OL" TO "01" then turns the remaining letter L into 1.

NOTE, ENTER, ON and USE FOR DEBUGGING (old code you may meet)

  • NOTE began a comment sentence in the Procedure Division. Replace it with a real comment (* in column 7, or *>). GnuCOBOL reports unknown statement 'NOTE' and, under -std=ibm, a syntax error. A NOTE swallows everything to the next full stop, so deleting only the first word changes the program.

  • ENTER named a language (ENTER LINKAGE, ENTER COBOL) so that a statement in another language could follow. It is obsolete in the 85 standard. cobc -std=ibm says 'ENTER' is a reserved word, but isn't supported. Replace it with a CALL to the other-language routine (see Subprograms: CALL and Linkage and Interoperability with C, Java and Python).

  • ON (ON 1 AND EVERY 5 DISPLAY "HEADER") ran a statement on the first and every n-th pass through the statement. It is not in the standard and not in Enterprise COBOL 6; cobc gives syntax error, unexpected ON. Replace it with a counter and an IF (IF CNT = 1 OR FUNCTION MOD(CNT, 5) = 0).

  • USE FOR DEBUGGING introduced a declarative that ran when a named item changed. GnuCOBOL accepts it silently by default, and -std=ibm says USE FOR DEBUGGING is obsolete in IBM COBOL (lax); in the test used here compiling with -debug did not run the declarative either, so do not count on it. The modern tools are the TEST compiler option with the interactive debugger on z/OS, and cobc -debug with gdb or DISPLAY tracing in GnuCOBOL (see Debugging and Troubleshooting).

Two-digit years (old code you may meet)

ACCEPT ... FROM DATE returns six digits, YYMMDD. Programs written before 2000 kept the year in two digits, and the shipped fix was a window: if the year is below 50 it is 20xx, otherwise 19xx. The bug moves to the year the window closes.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. DATE1.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  OLD-DATE             PIC 9(6).
       01  NEW-DATE             PIC 9(8).
       01  DUE-YY               PIC 99 VALUE 05.
       PROCEDURE DIVISION.
       MAIN-PARA.
           ACCEPT OLD-DATE FROM DATE.
           ACCEPT NEW-DATE FROM DATE YYYYMMDD.
           DISPLAY "LENGTHS " FUNCTION LENGTH(OLD-DATE) " "
               FUNCTION LENGTH(NEW-DATE).
           IF DUE-YY < 50
               DISPLAY "20" DUE-YY " (WINDOWED)"
           END-IF
           STOP RUN.

Compiled with cobc -x -Wall date1.cob, no warnings, it prints (the second line is the windowed date):

LENGTHS 6 8
2005 (WINDOWED)

The modern form keeps four digits end to end: ACCEPT ... FROM DATE YYYYMMDD or FUNCTION CURRENT-DATE (see Dates and Times), a four-digit year in every file and copybook, and date arithmetic with FUNCTION INTEGER-OF-DATE. Enterprise COBOL once had compiler-supported windowing (the DATEPROC and YEARWINDOW options); those names do not appear in the 6.5 manuals, so on a current compiler the window must be explicit in your own code or removed by widening the field. Widening a field changes the record length and every program that shares the copybook, which is why the Y2K projects took years; do it with a copybook change and a full recompile.

The other obsolete elements, in one table

Element What it was Modern replacement

REMARKS, AUTHOR, INSTALLATION, DATE-WRITTEN, DATE-COMPILED, SECURITY, comment entry

Documentation inside the program.

Comments, source control, the compiler listing.

MEMORY SIZE

Storage hint in OBJECT-COMPUTER.

Nothing; delete it.

MULTIPLE FILE TAPE, RERUN, REVERSED phrase, VALUE OF

Tape and checkpoint features of the 1960s and 1970s.

JCL/restart (RESTART, checkpoint-restart), OPEN ... INPUT; delete VALUE OF.

STOP literal

Stop and wait for the operator.

ACCEPT from the console or a plain STOP RUN.

Segmentation (SEGMENT-LIMIT, section numbers)

Overlay memory management.

Delete; the system handles storage.

DEBUG-ITEM, debugging sections

Debug facility.

Compiler TEST option and debugger.

ALL literal longer than one character on a numeric item

Fill a numeric item with a pattern.

MOVE with a proper value or INITIALIZE.

Migrating from OS/VS COBOL, VS COBOL II and Enterprise COBOL 4.2 to 6.x

Moving up a compiler version is the most common legacy task on the mainframe, and it is not only a recompile. Three stages are in play: OS/VS COBOL and VS COBOL II programs (1970s to 1990s), Enterprise COBOL 3 and 4 (to 4.2), and the rewritten compiler of Enterprise COBOL 5 and 6. IBM’s Migration Guide is the authoritative checklist (Enterprise COBOL for z/OS 6.5 Migration Guide); the points below are the ones that repeatedly cause defects.

From OS/VS COBOL and COBOL II

  • The OS/VS-only statements (EXAMINE, TRANSFORM, NOTE, ON, CURRENT-DATE and TIME-OF-DAY as special registers) must be rewritten as in the sections above. The CMPR2 compatibility option does not exist any more.

  • The 85-obsolete elements still compile, as the list at the top shows. Do not remove them in the same step as the compiler move.

  • LABEL RECORDS and similar clauses are ignored; keep them out of new code.

From Enterprise COBOL 4.2 to 6.x: invalid data

Enterprise COBOL 6 generates different code for numeric items, and a program that has been running with invalid data for years, because the older compiler happened to tolerate it, can change its output or abend with S0C7. The IBM text calls the data "invalid": a DISPLAY or PACKED-DECIMAL item with a non-digit, a wrong sign code or invalid zone bits, which a REDEFINES or a bad file can put there. Two options control the migration:

  • INVDATA tells the compiler not to assume that numeric data is valid: with NOINVDATA (the default) it generates the most efficient code and assumes valid data. INVDATA(NOFORCENUMCMP, CLEANSIGN) is the setting that most resembles the old compilers when you used NUMPROC(NOPFD) or NUMPROC(PFD), and INVDATA(FORCENUMCMP, NOCLEANSIGN) is closest to the old NUMPROC(MIG). INVDATA slows the program; IBM’s advice is to correct the data and go back to NOINVDATA.

  • NUMCHECK generates implicit class tests on the sending fields of DISPLAY, COMP-3 and binary items, so that bad data is reported with a message (MSG) or abends (ABD) at the statement. Use it in test to find the invalid data, then remove it.

IBM’s quick-reference table for choosing the settings, in words:

Coming from Invalid data present? Old setting Setting on 6.2 and later

Before COBOL 5

No

NUMPROC(MIG), NOPFD or PFD

NOINVDATA; NUMPROC(NOPFD) if it was MIG, otherwise the same NUMPROC

Before COBOL 5

Yes

NUMPROC(MIG)

INVDATA(FORCENUMCMP,NOCLEANSIGN) with NUMPROC(NOPFD)

Before COBOL 5

Yes

NUMPROC(NOPFD) or PFD

INVDATA(NOFORCENUMCMP,CLEANSIGN) (or just INVDATA) with the same NUMPROC

COBOL 5 or later

No

ZONEDATA(PFD)

NOINVDATA

COBOL 5 or later

Yes

ZONEDATA(NOPFD)

INVDATA

COBOL 5 or later

Yes

ZONEDATA(MIG)

INVDATA(FORCENUMCMP,CLEANSIGN)

IBM’s own example puts a blank in the middle of a PIC 9(4) item through a REDEFINES of PIC X(4) and shows that the result of IF value = ZERO depends on the compiler, the optimisation level and the option. The same shape in the Circulation scenario, run here with GnuCOBOL (which is a different compiler and uses its own rules, so this shows the hazard, not the IBM result):

       IDENTIFICATION DIVISION.
       PROGRAM-ID. INV1.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  COPIES-RAW           PIC X(4) VALUE "00 0".
       01  COPIES-NUM REDEFINES COPIES-RAW PIC 9(4).
       01  TOTAL-COPIES         PIC 9(5) VALUE 10.
       PROCEDURE DIVISION.
       MAIN-PARA.
           IF COPIES-NUM IS NUMERIC
               DISPLAY "COPIES is numeric"
           ELSE
               DISPLAY "COPIES is not numeric: " COPIES-RAW
           END-IF
           IF COPIES-NUM = ZERO
               DISPLAY "COPIES = ZERO is true"
           ELSE
               DISPLAY "COPIES = ZERO is false"
           END-IF
           ADD COPIES-NUM TO TOTAL-COPIES
           DISPLAY "TOTAL-COPIES " TOTAL-COPIES
           STOP RUN.

Compiled with cobc -x -Wall inv1.cob (and with -std=ibm), no warnings, it prints:

COPIES is not numeric: 00 0
COPIES = ZERO is true
TOTAL-COPIES 00010

The class test finds the problem and the comparison does not. The ADD quietly adds zero here; on z/OS with NOINVDATA the same statement may abend, and which of these two happens depends on the compiler settings. The fix is always the same: test with IS NUMERIC before using data from outside, and fix the producer. See Arithmetic and COMPUTE and USAGE and Numeric Representation.

Other changes to check

  • ARCH and TUNE. A program compiled for a higher ARCH level does not run on older hardware. In 6.5, ARCH(11) is the default and ARCH(10) was removed, while ARCH(15) and TUNE(15) target the z17.

  • SMARTBIN. Adds binary metadata that IBM’s Automatic Binary Optimizer can use later (introduced in 6.4; a NAMES suboption adds symbol names in 6.5). It is not supported with LP(64), and module size grows. See Compiling, Linking and Language Environment.

  • Read and write behaviour. The Programming Guide points to "wrong length READ" messages for finding record-length errors that older compilers hid.

  • Program objects and AMODE. Programs compile into program objects (PDSE), not load modules in a PDS, and 64-bit AMODE interop has its own rules (see the Compiling page and Performance and Compiler Options).

  • Behaviour on incorrect programs. The Migration Guide section on error behaviour changes lists programs that used to work by accident.

The sequence that works: inventory the compilers and options in use; recompile with 6.x with the old NUMPROC and INVDATA settings that match; run the regression tests or a parallel run (see Testing COBOL Programs); turn on NUMCHECK in test; fix the data; go to NOINVDATA; only then optimise with ARCH, TUNE and OPTIMIZE.

Moving code between compilers: IBM to GnuCOBOL (and back)

A program written for Enterprise COBOL can often be compiled by GnuCOBOL for development and testing on a PC. The dialect switch is -std=ibm (and -std=ibm-strict to reject extensions); it changes the reserved-word list and a set of configuration values, but it does not make GnuCOBOL an IBM compiler. The checks below cover the differences that change results rather than just compile errors; the complete dialect story is on Compilers and Dialects.

Collating sequence

z/OS compares characters in EBCDIC order: lower-case letters, then upper-case, then digits. GnuCOBOL on Linux or macOS compares in ASCII order: digits, then upper-case, then lower-case. A comparison, a SORT or a table kept in key order can therefore give a different answer. Compiled with cobc -x -Wall collate.cob, which declares an alphabet but does not use it:

native order: 1A sorts before A1

Naming it in OBJECT-COMPUTER with PROGRAM COLLATING SEQUENCE IS EBCDIC-ORDER (program COLLATE2, same declarations):

program collating sequence EBCDIC: A1 first
       IDENTIFICATION DIVISION.
       PROGRAM-ID. COLLATE2.
       ENVIRONMENT DIVISION.
       CONFIGURATION SECTION.
       OBJECT-COMPUTER. CIRC-HOST
           PROGRAM COLLATING SEQUENCE IS EBCDIC-ORDER.
       SPECIAL-NAMES.
           ALPHABET EBCDIC-ORDER IS EBCDIC.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  CODE-A               PIC X(2) VALUE "A1".
       01  CODE-B               PIC X(2) VALUE "1A".
       PROCEDURE DIVISION.
       MAIN-PARA.
           IF CODE-A < CODE-B
               DISPLAY "program collating sequence EBCDIC: A1 first"
           ELSE
               DISPLAY "program collating sequence EBCDIC: 1A first"
           END-IF
           STOP RUN.

-std=ibm gives the same two results. The clause applies to comparisons in the program, so use it when the data or the expected order comes from z/OS, and test the order of any file that SORT or MERGE produces and a downstream program reads (see SORT and MERGE).

COMP sizes and truncation

IBM stores PIC 9(1) to 9(4) binary items in two bytes, five to nine digits in four bytes and ten to eighteen in eight bytes. GnuCOBOL’s default dialect also uses one byte for a single digit (binary-size: 1-2-4-8); -std=ibm uses 2-4-8. The size matters in record layouts and for LENGTH, and so does what happens when a value is too big for the PICTURE:

       IDENTIFICATION DIVISION.
       PROGRAM-ID. SIZES.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  B1                   PIC 9(1)  COMP.
       01  B4                   PIC 9(4)  COMP.
       01  B5                   PIC 9(5)  COMP.
       01  B9                   PIC 9(9)  COMP.
       01  B10                  PIC 9(10) COMP.
       01  B18                  PIC 9(18) COMP.
       01  P7                   PIC S9(7) COMP-3.
       01  CNT                  PIC 9(4)  COMP VALUE 0.
       PROCEDURE DIVISION.
       MAIN-PARA.
           DISPLAY "COMP 9(1)  " FUNCTION LENGTH(B1)
           DISPLAY "COMP 9(4)  " FUNCTION LENGTH(B4)
           DISPLAY "COMP 9(5)  " FUNCTION LENGTH(B5)
           DISPLAY "COMP 9(9)  " FUNCTION LENGTH(B9)
           DISPLAY "COMP 9(10) " FUNCTION LENGTH(B10)
           DISPLAY "COMP 9(18) " FUNCTION LENGTH(B18)
           DISPLAY "COMP-3 S9(7) " FUNCTION LENGTH(P7)
           MOVE 12345 TO CNT
           DISPLAY "12345 into 9(4) COMP: " CNT
           STOP RUN.

Compiled with cobc -x -Wall sizes.cob it warns about the MOVE (value size exceeds data size) and prints the default-dialect sizes below; with -std=ibm it has no warning and prints the second column.

Item Default dialect -std=ibm

PIC 9(1) COMP

1 byte

2 bytes

PIC 9(4) COMP

2 bytes

2 bytes

PIC 9(5) COMP

4 bytes

4 bytes

PIC 9(9) COMP

4 bytes

4 bytes

PIC 9(10) COMP

8 bytes

8 bytes

PIC 9(18) COMP

8 bytes

8 bytes

PIC S9(7) COMP-3

4 bytes

4 bytes

MOVE 12345 into PIC 9(4) COMP

stores 2345 (truncated to the PICTURE)

stores 12345 (the dialect sets binary-truncate: no)

The byte order is big-endian in both dialects, like IBM Z; COMP-5 is native (little-endian on x86). On z/OS the TRUNC option decides what happens to the value and costs performance in the TRUNC(STD) mode; to make a GnuCOBOL test match TRUNC(STD), compile with -fbinary-truncate (observed here: the value is then reduced to the digits the 2-byte item holds). If you compare the results of the two compilers on arithmetic, compare these settings first.

The rest of the checklist

  • Source format. Fixed format with CBL/PROCESS statements on z/OS; cobc ignores compiler directives it does not know and warns; check the -fformat option (see Reference Format).

  • Files. ASSIGN TO names a DD name on z/OS and a file name or environment variable in GnuCOBOL; LINE SEQUENTIAL exists on z/OS only for UNIX files. Re-run the file status checks (see File Status Codes).

  • Subsystems. EXEC SQL and EXEC CICS do not compile without a preprocessor or emulator (see Embedded SQL and Db2); Language Environment callable services (CEE...) are IBM-only.

  • Intrinsic and runtime differences. GnuCOBOL 3.2 changed the FUNCTION RANDOM algorithm and the sizes of BY VALUE arguments on 64-bit; neither equals the IBM result.

  • Going the other way. A program developed with GnuCOBOL and -std=default may use extensions IBM lacks (TRANSFORM, LINE SEQUENTIAL everywhere, ACCEPT ... FROM ENVIRONMENT, --coverage); compile with -std=ibm-strict before you promote it.

Common mistakes

  • Changing the compiler and the code at once. Move the compiler first, with settings that mimic the old behaviour, then clean up.

  • Deleting ALTER without tracing every target. The hidden state is the program.

  • Turning on INVDATA and leaving it on. It hides data defects and costs speed. Use it to buy time, not as the final setting.

  • Testing on ASCII and trusting EBCDIC order. Test sorts and comparisons with the collating sequence of production.

  • Fixing two-digit years by widening one program. The copybook and every file layout have to change together.