Reports and Report Writer

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 WRITE ... ADVANCING and LINAGE come from COBOL 74 and 85 and the Report Writer, an optional module of COBOL 85, is part of the standard again since COBOL 2002; against IBM Enterprise COBOL for z/OS 6.5 (PTFs through September 2026) on z/OS 3.2, which supports the print statements and clauses but has no Report Writer module of its own; 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, which printed the same results unless the text says otherwise). z/OS examples are labelled "z/OS; not run here" and are checked against the Language Reference and the Programming Guide.

A report is a file of print lines. This page builds the Circulation reports in three steps: by hand with print-line layouts and WRITE AFTER ADVANCING, with page control through LINAGE, and with the Report Writer, which describes the report instead of coding it. The central idea is the control break: a summary line is printed whenever a key (the member, the date) changes in a sorted file. The input has to be in key order, which is the job of Sort and Merge. The file statements are on Sequential Files, and the edited pictures used for numbers and dates (ZZ9, 9999/99/99) are on The PICTURE Clause.

Where the book is outdated: Taulli’s Modern Mainframe Development (2022, chapter 6) treats report layout and testing as a matter of print lines, edited pictures and carriage control, and that is still how most z/OS batch reports are written. It does not mention the Report Writer. The Report Writer is a legitimate, standard COBOL feature that GnuCOBOL implements, and a section below shows it; on Enterprise COBOL a separate precompiler product would be required, so the hand-coded form remains the portable choice for z/OS.

The print line

A print file is a sequential file whose records are lines of text. The program builds each line in WORKING-STORAGE as a group of fields with FILLER for the spaces between, and writes it with WRITE ... FROM. Column headings are constants, and detail lines use edited pictures that insert spaces, slashes, commas, signs and a decimal point and suppress leading zeros. The record of the print file is wide enough for the widest line: 132 print positions is the traditional line printer width.

01  WS-DETAIL.
    05  DL-MEMBER   PIC 9(6).
    05  FILLER      PIC XX    VALUE SPACES.
    05  DL-TYPE     PIC X(10).
    05  DL-BOOK     PIC 9(6).
    05  FILLER      PIC XX    VALUE SPACES.
    05  DL-DATE     PIC 9999/99/99.

The FILLER items need VALUE SPACES because a print line built in storage is moved into the record as it is: unused bytes would carry whatever the storage held. The same rule applies to a line that is built field by field in the record itself, which is why MOVE SPACES TO PRINT-LINE comes first in that style.

WRITE with ADVANCING

WRITE record-name [FROM identifier]
    {BEFORE | AFTER} ADVANCING {integer-or-identifier {LINE | LINES} | PAGE}
  • AFTER ADVANCING n LINES moves the paper first and then prints the line: AFTER ADVANCING 2 LINES leaves one blank line before it. BEFORE ADVANCING prints first and then moves. PAGE goes to the first printable line of the next page (the next logical page when the file has LINAGE). If the device cannot do PAGE, one line is advanced.

  • Without an ADVANCING phrase, a WRITE on a print file behaves as AFTER ADVANCING 1 LINE. Use one style throughout a print file: all AFTER, or all BEFORE. Mixing them is allowed by the standard but produces different spacing on different systems (see the first test below).

  • An ADVANCING phrase (or a LINAGE clause) makes the system put a carriage-control character in the first byte of each record, so the printer or the spool knows what to do. With AFTER only the characters are ASA characters (a blank for one line, 0 for two, - for three, + for no advance, 1 for a new page) and the data set has to be RECFM=FBA; with BEFORE the machine characters are used and the data set is FBM. The compiler option ADV adds one byte to each record for the character; with NOADV the program reserves the first byte itself. A print file for 132 columns is therefore declared with LRECL=133 and RECFM=FBA.

  • WRITE ... FROM keeps the source line unchanged after the write, so a heading can be written again on each page.

A report with one control break

The first report lists the loan transactions grouped by member, with a count after each member and a grand total. LOANBYM is the transactions sorted by member (shell: sort -s -k1.2,1.7 LOANTRAN > LOANBYM; the COBOL sort is on the sort page). The program prints the heading, reads ahead, compares each record’s member with the previous one and prints the member total when it changes.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. CBREAK1.
      * Circulation report with a control break on the member.
       ENVIRONMENT DIVISION.
       INPUT-OUTPUT SECTION.
       FILE-CONTROL.
           SELECT LOAN-FILE ASSIGN TO "LOANBYM"
               ORGANIZATION IS LINE SEQUENTIAL.
           SELECT PRINT-FILE ASSIGN TO "REPORT1"
               ORGANIZATION IS LINE SEQUENTIAL.
       DATA DIVISION.
       FILE SECTION.
       FD  LOAN-FILE.
       01  LOAN-LINE           PIC X(30).
       FD  PRINT-FILE.
       01  PRINT-LINE          PIC X(60).
       WORKING-STORAGE SECTION.
       COPY LOANREC.
       01  WS-EOF              PIC X VALUE "N".
           88  END-OF-LOANS    VALUE "Y".
       01  WS-PREV-MEMBER      PIC 9(6) VALUE ZERO.
       01  WS-MEMBER-COUNT     PIC 9(3) VALUE ZERO.
       01  WS-TOTAL-COUNT      PIC 9(3) VALUE ZERO.
       01  WS-HEADING-1.
           05  FILLER          PIC X(30)
               VALUE "CIRCULATION REPORT".
           05  FILLER          PIC X(20) VALUE SPACES.
       01  WS-HEADING-2.
           05  FILLER          PIC X(8)  VALUE "MEMBER".
           05  FILLER          PIC X(10) VALUE "TYPE".
           05  FILLER          PIC X(8)  VALUE "BOOK".
           05  FILLER          PIC X(8)  VALUE "DATE".
       01  WS-DETAIL.
           05  DL-MEMBER       PIC 9(6).
           05  FILLER          PIC XX    VALUE SPACES.
           05  DL-TYPE         PIC X(10).
           05  DL-BOOK         PIC 9(6).
           05  FILLER          PIC XX    VALUE SPACES.
           05  DL-DATE         PIC 9999/99/99.
       01  WS-MEMBER-TOTAL.
           05  FILLER          PIC X(9)  VALUE "  member ".
           05  MT-MEMBER       PIC 9(6).
           05  FILLER          PIC X(15) VALUE " transactions: ".
           05  MT-COUNT        PIC ZZ9.
       01  WS-GRAND-TOTAL.
           05  FILLER          PIC X(20) VALUE "Total transactions: ".
           05  GT-COUNT        PIC ZZZ9.
       PROCEDURE DIVISION.
       0000-MAIN-LINE.
           OPEN INPUT LOAN-FILE OUTPUT PRINT-FILE
           WRITE PRINT-LINE FROM WS-HEADING-1 AFTER ADVANCING 1 LINE
           WRITE PRINT-LINE FROM WS-HEADING-2 AFTER ADVANCING 2 LINES
           PERFORM 8000-READ-LOAN
           MOVE LOAN-MEMBER-ID TO WS-PREV-MEMBER
           PERFORM UNTIL END-OF-LOANS
               IF LOAN-MEMBER-ID NOT = WS-PREV-MEMBER
                   PERFORM 3000-MEMBER-BREAK
               END-IF
               PERFORM 2000-PRINT-DETAIL
               PERFORM 8000-READ-LOAN
           END-PERFORM
           PERFORM 3000-MEMBER-BREAK
           MOVE WS-TOTAL-COUNT TO GT-COUNT
           WRITE PRINT-LINE FROM WS-GRAND-TOTAL AFTER ADVANCING 2 LINES
           CLOSE LOAN-FILE PRINT-FILE
           STOP RUN.
       2000-PRINT-DETAIL.
           MOVE LOAN-MEMBER-ID TO DL-MEMBER
           IF LOAN-CHECKOUT
               MOVE "checkout" TO DL-TYPE
           ELSE
               MOVE "return" TO DL-TYPE
           END-IF
           MOVE LOAN-BOOK-ID TO DL-BOOK
           MOVE LOAN-DATE TO DL-DATE
           WRITE PRINT-LINE FROM WS-DETAIL AFTER ADVANCING 1 LINE
           ADD 1 TO WS-MEMBER-COUNT WS-TOTAL-COUNT.
       3000-MEMBER-BREAK.
           MOVE WS-PREV-MEMBER TO MT-MEMBER
           MOVE WS-MEMBER-COUNT TO MT-COUNT
           WRITE PRINT-LINE FROM WS-MEMBER-TOTAL
               AFTER ADVANCING 1 LINE
           MOVE ZERO TO WS-MEMBER-COUNT
           MOVE LOAN-MEMBER-ID TO WS-PREV-MEMBER.
       8000-READ-LOAN.
           READ LOAN-FILE INTO LOAN-RECORD
               AT END SET END-OF-LOANS TO TRUE
           END-READ.

Compiled with cobc -x -Wall -I. cbreak1.cob (and with -std=ibm), no warnings, it writes REPORT1:

CIRCULATION REPORT

MEMBER  TYPE      BOOK    DATE
000100  checkout  000001  2026/10/20
000100  return    000001  2026/10/20
  member 000100 transactions:   2
000101  checkout  000002  2026/10/20
  member 000101 transactions:   1
000102  checkout  000003  2026/10/20
  member 000102 transactions:   1

Total transactions:    4

The first line of the file is empty because the first WRITE used AFTER ADVANCING 1 LINE: the paper moves one line before anything is printed. On a printer that is the top margin; in a text file it is a blank first line.

The structure of the control break is the part to learn:

  • The loop reads ahead: the first record is read before the loop, and each iteration compares the current record with the previous key, prints the total of the group that just ended if they differ, and then processes the current record.

  • The previous key is saved in a working-storage field. After a break the saved key is set to the new key and the group counter to zero.

  • After the end of the file the last group has not been closed, so the program performs the break paragraph once more, then prints the grand total.

  • The first record must not cause a break: the program sets the previous key from the first record before the loop starts.

A mix of print styles is a trap. In the first version of the program the first two WRITE statements were WRITE PRINT-LINE FROM WS-HEADING-1 (no phrase) and WRITE PRINT-LINE FROM WS-HEADING-2 AFTER ADVANCING 2 LINES, and the detail lines had no phrase either. GnuCOBOL 3.2.0 put the line terminator after the lines written without a phrase and before the lines written with AFTER, so the second heading ran into the first detail line:

MEMBER  TYPE  BOOK    DATE000100  checkout000001  2026/10/20

The fix is the one in the program above: every WRITE on the print file says AFTER ADVANCING.

Page control with LINAGE

Printing a page header and counting lines by hand works, but the LINAGE clause lets the system count them:

FD  PRINT-FILE
    LINAGE IS body-lines LINES
        [WITH FOOTING AT footing-line]
        [LINES AT TOP top-lines]
        [LINES AT BOTTOM bottom-lines].
  • The body is the number of lines on which the program prints. The top and bottom margins are blank lines before and after the body; the logical page is the body plus the two margins. FOOTING AT marks the line where the footing area starts, inside the body; if it is missing the footing area is the end of the body.

  • The special register LINAGE-COUNTER holds the current line number within the body. PAGE resets it to 1; a write adds the number of lines advanced.

  • AT END-OF-PAGE (or EOP) on a WRITE runs after the line is printed, when the line fell in the footing area. It is the place for the page footing and the next page heading. If a line does not fit in the body, the system moves to the next page first (the page overflow), then runs the EOP statement.

  • LINAGE applies to files opened OUTPUT or EXTEND, and not to VSAM files. In an SD it is syntax-checked and ignored.

  • The values can be integers or data names; a data name is read at OPEN OUTPUT and again at each new page.

The program below prints the book list on tiny pages of five body lines with a footing area from line 4, so that the page break is visible. The first heading is written with AFTER ADVANCING 1 LINE because a PAGE eject on a brand new file only adds blank lines; later headings use ADVANCING PAGE.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. LINAGE1.
      * Book list on short pages: LINAGE, LINAGE-COUNTER, END-OF-PAGE.
       ENVIRONMENT DIVISION.
       INPUT-OUTPUT SECTION.
       FILE-CONTROL.
           SELECT BOOK-FILE ASSIGN TO "BOOKMAST"
               ORGANIZATION IS LINE SEQUENTIAL.
           SELECT PRINT-FILE ASSIGN TO "REPORT3"
               ORGANIZATION IS LINE SEQUENTIAL.
       DATA DIVISION.
       FILE SECTION.
       FD  BOOK-FILE.
       01  BOOK-LINE           PIC X(60).
       FD  PRINT-FILE
           LINAGE IS 5 LINES
               WITH FOOTING AT 4
               LINES AT TOP 1
               LINES AT BOTTOM 1.
       01  PRINT-LINE          PIC X(40).
       WORKING-STORAGE SECTION.
       COPY BOOKREC.
       01  WS-EOF              PIC X VALUE "N".
           88  END-OF-BOOKS    VALUE "Y".
       01  WS-PAGE             PIC 99 VALUE ZERO.
       01  WS-HEADING.
           05  FILLER          PIC X(20) VALUE "BOOK LIST     PAGE ".
           05  HD-PAGE         PIC Z9.
       01  WS-DETAIL.
           05  DT-ID           PIC 9(6).
           05  FILLER          PIC XX VALUE SPACES.
           05  DT-TITLE        PIC X(30).
       01  WS-FOOTING          PIC X(40) VALUE "-- continued --".
       PROCEDURE DIVISION.
       0000-MAIN-LINE.
           OPEN INPUT BOOK-FILE OUTPUT PRINT-FILE
           PERFORM 2000-NEW-PAGE
           PERFORM UNTIL END-OF-BOOKS
               READ BOOK-FILE INTO BOOK-RECORD
                   AT END SET END-OF-BOOKS TO TRUE
                   NOT AT END PERFORM 3000-PRINT-BOOK
               END-READ
           END-PERFORM
           DISPLAY "LINAGE-COUNTER at the end: " LINAGE-COUNTER
           CLOSE BOOK-FILE PRINT-FILE
           STOP RUN.
       2000-NEW-PAGE.
           ADD 1 TO WS-PAGE
           MOVE WS-PAGE TO HD-PAGE
      * The first heading needs no page eject on a new file.
           IF WS-PAGE = 1
               WRITE PRINT-LINE FROM WS-HEADING
                   AFTER ADVANCING 1 LINE
           ELSE
               WRITE PRINT-LINE FROM WS-HEADING
                   AFTER ADVANCING PAGE
           END-IF.
       3000-PRINT-BOOK.
           MOVE BOOK-ID TO DT-ID
           MOVE BOOK-TITLE TO DT-TITLE
           WRITE PRINT-LINE FROM WS-DETAIL
               AFTER ADVANCING 1 LINE
               AT END-OF-PAGE
                   WRITE PRINT-LINE FROM WS-FOOTING
                       AFTER ADVANCING 1 LINE
                   PERFORM 2000-NEW-PAGE
           END-WRITE.

Compiled with cobc -x -Wall -I. linage1.cob (and with -std=ibm), no warnings, it writes REPORT3 and shows the value of LINAGE-COUNTER at the end:

LINAGE-COUNTER at the end: 0000000003
BOOK LIST     PAGE   1
000001  DATA STRUCTURES
000002  COBOL BASICS
-- continued --


BOOK LIST     PAGE   2
000003  BATCH PROCESSING
000004  OLD LEDGERS

The top margin is the blank first line, and the first page holds the heading, two books and the -- continued -- footing at body line 4, which triggered the end-of-page condition. The two blank lines before page 2 are the bottom margin of page 1 and the top margin of page 2. On z/OS the LINAGE file must be allocated RECFM=FBA (or the system changes the record format), and the control characters are generated; for a SYSOUT data set the characters are what the printer or the spool reads.

Control breaks on two levels

A report with a hierarchy has one break per level. The nightly Circulation history report is sorted by date (the major key) and, within a date, by member (the minor key). It prints a total after each member, a total after each date and a grand total. The input is LOANHIST, six transactions of two days, in date, member order:

O00010000000220261019
O00010100000120261019
R00010100000120261019
O00010000000120261020
R00010000000220261020
O00010200000320261020

The rules of a multi-level break:

  • When a major key changes, all lower levels end too, and the lower-level total is printed first (minor, then major). When only the minor key changes, only the minor total is printed.

  • Each level has its own accumulator. When a minor group ends, its totals are added to the next level up, and then reset. Adding at the break, not at each record, keeps the work small.

  • The break test runs before the current record is processed and compares with the saved keys, which still describe the previous record.

  • At the end of the file all open levels are closed, minor first, then the final totals are printed.

flowchart TD A["Read the first record, save its date and member, print the date heading"] --> B{"End of file?"} B -- "Yes" --> L["Member total, then day total, then grand total"] B -- "No" --> C{"Date changed?"} C -- "Yes" --> D["Print member total, add to day, print day total, add to grand, print new date heading"] C -- "No" --> E{"Member changed?"} E -- "Yes" --> F["Print member total, add to day, reset member counters"] E -- "No" --> G["Count the record"] D --> G F --> G G --> H["Read next record"] H --> B

The figure shows the order of the totals: a change of date closes the member first, then the day, and a change of member only the member. The program is the one-level program extended with a second set of counters and the EVALUATE that tests the major key first.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. CBREAK2.
      * Two-level control break: date (major), member (minor).
       ENVIRONMENT DIVISION.
       INPUT-OUTPUT SECTION.
       FILE-CONTROL.
           SELECT LOAN-FILE ASSIGN TO "LOANHIST"
               ORGANIZATION IS LINE SEQUENTIAL.
           SELECT PRINT-FILE ASSIGN TO "REPORT2"
               ORGANIZATION IS LINE SEQUENTIAL.
       DATA DIVISION.
       FILE SECTION.
       FD  LOAN-FILE.
       01  LOAN-LINE           PIC X(30).
       FD  PRINT-FILE.
       01  PRINT-LINE          PIC X(60).
       WORKING-STORAGE SECTION.
       COPY LOANREC.
       01  WS-EOF              PIC X VALUE "N".
           88  END-OF-LOANS    VALUE "Y".
       01  WS-PREV-DATE        PIC 9(8) VALUE ZERO.
       01  WS-PREV-MEMBER      PIC 9(6) VALUE ZERO.
       01  WS-MEMBER-TOTALS.
           05  WS-MEMBER-OUT   PIC 9(3) VALUE ZERO.
           05  WS-MEMBER-IN    PIC 9(3) VALUE ZERO.
       01  WS-DATE-TOTALS.
           05  WS-DATE-OUT     PIC 9(3) VALUE ZERO.
           05  WS-DATE-IN      PIC 9(3) VALUE ZERO.
       01  WS-GRAND-TOTALS.
           05  WS-GRAND-OUT    PIC 9(3) VALUE ZERO.
           05  WS-GRAND-IN     PIC 9(3) VALUE ZERO.
       01  WS-DATE-HEADING.
           05  FILLER          PIC X(6)  VALUE "DATE: ".
           05  DH-DATE         PIC 9999/99/99.
       01  WS-MEMBER-LINE.
           05  FILLER          PIC X(4)  VALUE SPACES.
           05  FILLER          PIC X(8)  VALUE "Member ".
           05  ML-MEMBER       PIC 9(6).
           05  FILLER          PIC X(11) VALUE "  checkouts".
           05  ML-OUT          PIC ZZ9.
           05  FILLER          PIC X(10) VALUE "   returns".
           05  ML-IN           PIC ZZ9.
       01  WS-DATE-LINE.
           05  FILLER          PIC X(18) VALUE "  Day total       ".
           05  FILLER          PIC X(11) VALUE "  checkouts".
           05  DL-OUT          PIC ZZ9.
           05  FILLER          PIC X(10) VALUE "   returns".
           05  DL-IN           PIC ZZ9.
       01  WS-GRAND-LINE.
           05  FILLER          PIC X(18) VALUE "Grand total       ".
           05  FILLER          PIC X(11) VALUE "  checkouts".
           05  GL-OUT          PIC ZZ9.
           05  FILLER          PIC X(10) VALUE "   returns".
           05  GL-IN           PIC ZZ9.
       PROCEDURE DIVISION.
       0000-MAIN-LINE.
           OPEN INPUT LOAN-FILE OUTPUT PRINT-FILE
           PERFORM 8000-READ-LOAN
           IF NOT END-OF-LOANS
               MOVE LOAN-DATE TO WS-PREV-DATE
               MOVE LOAN-MEMBER-ID TO WS-PREV-MEMBER
               PERFORM 3100-DATE-HEADING
           END-IF
           PERFORM UNTIL END-OF-LOANS
               EVALUATE TRUE
                   WHEN LOAN-DATE NOT = WS-PREV-DATE
                       PERFORM 3000-MEMBER-BREAK
                       PERFORM 4000-DATE-BREAK
                       PERFORM 3100-DATE-HEADING
                   WHEN LOAN-MEMBER-ID NOT = WS-PREV-MEMBER
                       PERFORM 3000-MEMBER-BREAK
               END-EVALUATE
               PERFORM 2000-COUNT-LOAN
               PERFORM 8000-READ-LOAN
           END-PERFORM
           PERFORM 3000-MEMBER-BREAK
           PERFORM 4000-DATE-BREAK
           MOVE WS-GRAND-OUT TO GL-OUT
           MOVE WS-GRAND-IN TO GL-IN
           WRITE PRINT-LINE FROM WS-GRAND-LINE
               AFTER ADVANCING 2 LINES
           CLOSE LOAN-FILE PRINT-FILE
           STOP RUN.
       2000-COUNT-LOAN.
           IF LOAN-CHECKOUT
               ADD 1 TO WS-MEMBER-OUT
           ELSE
               ADD 1 TO WS-MEMBER-IN
           END-IF.
       3000-MEMBER-BREAK.
           MOVE WS-PREV-MEMBER TO ML-MEMBER
           MOVE WS-MEMBER-OUT TO ML-OUT
           MOVE WS-MEMBER-IN TO ML-IN
           WRITE PRINT-LINE FROM WS-MEMBER-LINE
               AFTER ADVANCING 1 LINE
           ADD WS-MEMBER-OUT TO WS-DATE-OUT
           ADD WS-MEMBER-IN TO WS-DATE-IN
           INITIALIZE WS-MEMBER-TOTALS
           MOVE LOAN-MEMBER-ID TO WS-PREV-MEMBER.
       3100-DATE-HEADING.
           MOVE LOAN-DATE TO DH-DATE
           WRITE PRINT-LINE FROM WS-DATE-HEADING
               AFTER ADVANCING 2 LINES.
       4000-DATE-BREAK.
           MOVE WS-DATE-OUT TO DL-OUT
           MOVE WS-DATE-IN TO DL-IN
           WRITE PRINT-LINE FROM WS-DATE-LINE
               AFTER ADVANCING 1 LINE
           ADD WS-DATE-OUT TO WS-GRAND-OUT
           ADD WS-DATE-IN TO WS-GRAND-IN
           INITIALIZE WS-DATE-TOTALS
           MOVE LOAN-DATE TO WS-PREV-DATE.
       8000-READ-LOAN.
           READ LOAN-FILE INTO LOAN-RECORD
               AT END SET END-OF-LOANS TO TRUE
           END-READ.

Compiled with cobc -x -Wall -I. cbreak2.cob (and with -std=ibm), no warnings, it writes REPORT2:

DATE: 2026/10/19
    Member  000100  checkouts  1   returns  0
    Member  000101  checkouts  1   returns  1
  Day total         checkouts  2   returns  1

DATE: 2026/10/20
    Member  000100  checkouts  1   returns  1
    Member  000102  checkouts  1   returns  0
  Day total         checkouts  2   returns  1

Grand total         checkouts  4   returns  2

The grand total (4 checkouts, 2 returns) is the sum of the two days, and each day is the sum of its members. The control totals are what an operator compares with the transaction count of the previous job step. If a third level is needed (a branch above the date, say), the pattern repeats: a test for the new major key first, with the cascade of breaks downward.

The Report Writer

The Report Writer moves the layout, the page handling and the control breaks out of the procedure code and into the Data Division. The program describes the report with an RD (report description) and its report groups, then uses three statements: INITIATE, GENERATE and TERMINATE. The system works out the page breaks, the control breaks and the totals.

Support

The Report Writer is part of the COBOL standard (COBOL 85 had it as an optional module, COBOL 2002 and later include it). GnuCOBOL 3.2 implements it. Enterprise COBOL for z/OS 6.5 does not include it. IBM’s route for Report Writer programs was a separate precompiler product that translates the Report Writer statements into ordinary COBOL; check the current availability with IBM before planning a z/OS use. On z/OS the hand-coded form above, or a DFSORT OUTFIL report (HEADER1, SECTIONS, TRAILER), is the usual substitute.

The pieces

  • FD ... REPORT IS report-name. The output file names the report; it has no record description of its own (the report groups supply the lines).

  • REPORT SECTION in the Data Division, with an RD report-name:

    • CONTROLS ARE FINAL control-1 control-2 ... (or CONTROL IS): the control fields from the most to the least significant; FINAL is the break after the last record. The fields are data items of the input record or of working storage.

    • PAGE LIMIT IS n LINES, HEADING n, FIRST DETAIL n, LAST DETAIL n, FOOTING n: the page layout.

  • Report groups (level 01 in the RD), each with a TYPE:

    • REPORT HEADING and REPORT FOOTING: once at the start and the end of the report.

    • PAGE HEADING and PAGE FOOTING: on every page; PAGE-COUNTER is a special register.

    • CONTROL HEADING and CONTROL FOOTING (with a control name or FINAL): when the control field starts and ends a group.

    • DETAIL: printed by each GENERATE.

  • Lines and columns. Inside a group, LINE n or LINE PLUS n sets the line, COLUMN n the starting position, PIC the picture. An item gets its value from VALUE (a constant), SOURCE (copied from a data item when the line is printed), or SUM (a running total of one or more data items, added at each GENERATE and cleared at the footing). A SUM in a lower-level footing is rolled into the footing of the next level up automatically.

  • Statements. INITIATE report-name opens the report and clears the sums; GENERATE detail-name prints a detail group (and any headings or footings the control fields call for); TERMINATE report-name prints the last footings and the report footing.

The Circulation report in the Report Writer

This program produces the same report as the hand-coded two-level program, from the same LOANHIST. The control fields are LOAN-DATE and LOAN-MEMBER-ID; the counts come from two working-storage flags that hold 1 or 0 for each transaction, because a SUM adds data items:

       IDENTIFICATION DIVISION.
       PROGRAM-ID. RWDEMO.
      * Standard Report Writer: the two-level control break report.
       ENVIRONMENT DIVISION.
       INPUT-OUTPUT SECTION.
       FILE-CONTROL.
           SELECT LOAN-FILE ASSIGN TO "LOANHIST"
               ORGANIZATION IS LINE SEQUENTIAL.
           SELECT REPORT-FILE ASSIGN TO "REPORT4"
               ORGANIZATION IS LINE SEQUENTIAL.
       DATA DIVISION.
       FILE SECTION.
       FD  LOAN-FILE.
       COPY LOANREC.
       FD  REPORT-FILE
           REPORT IS CIRCULATION-REPORT.
       WORKING-STORAGE SECTION.
       01  WS-EOF              PIC X VALUE "N".
           88  END-OF-LOANS    VALUE "Y".
       01  WS-OUT-ONE          PIC 9 VALUE ZERO.
       01  WS-IN-ONE           PIC 9 VALUE ZERO.
       01  WS-TYPE-TEXT        PIC X(8).
       REPORT SECTION.
       RD  CIRCULATION-REPORT
           CONTROLS ARE FINAL LOAN-DATE LOAN-MEMBER-ID
           PAGE LIMIT IS 30 LINES
           HEADING 1
           FIRST DETAIL 4
           LAST DETAIL 28.
       01  TYPE PAGE HEADING.
           05  LINE 1.
               10  COLUMN 1   PIC X(18) VALUE "CIRCULATION REPORT".
               10  COLUMN 30  PIC X(5)  VALUE "PAGE ".
               10  COLUMN 35  PIC Z9    SOURCE PAGE-COUNTER.
       01  TYPE CONTROL HEADING LOAN-DATE.
           05  LINE PLUS 2.
               10  COLUMN 1   PIC X(6)  VALUE "DATE: ".
               10  COLUMN 7   PIC 9999/99/99 SOURCE LOAN-DATE.
       01  DETAIL-LINE TYPE DETAIL.
           05  LINE PLUS 1.
               10  COLUMN 5   PIC 9(6)  SOURCE LOAN-MEMBER-ID.
               10  COLUMN 13  PIC X(8)  SOURCE WS-TYPE-TEXT.
               10  COLUMN 23  PIC 9(6)  SOURCE LOAN-BOOK-ID.
       01  TYPE CONTROL FOOTING LOAN-MEMBER-ID.
           05  LINE PLUS 1.
               10  COLUMN 5   PIC X(7)  VALUE "Member ".
               10  COLUMN 12  PIC 9(6)  SOURCE LOAN-MEMBER-ID.
               10  COLUMN 22  PIC X(10) VALUE "checkouts ".
               10  COLUMN 32  PIC ZZ9   SUM WS-OUT-ONE.
               10  COLUMN 38  PIC X(8)  VALUE "returns ".
               10  COLUMN 46  PIC ZZ9   SUM WS-IN-ONE.
       01  TYPE CONTROL FOOTING LOAN-DATE.
           05  LINE PLUS 1.
               10  COLUMN 3   PIC X(10) VALUE "Day total ".
               10  COLUMN 22  PIC X(10) VALUE "checkouts ".
               10  COLUMN 32  PIC ZZ9   SUM WS-OUT-ONE.
               10  COLUMN 38  PIC X(8)  VALUE "returns ".
               10  COLUMN 46  PIC ZZ9   SUM WS-IN-ONE.
       01  TYPE CONTROL FOOTING FINAL.
           05  LINE PLUS 2.
               10  COLUMN 1   PIC X(11) VALUE "Grand total".
               10  COLUMN 22  PIC X(10) VALUE "checkouts ".
               10  COLUMN 32  PIC ZZ9   SUM WS-OUT-ONE.
               10  COLUMN 38  PIC X(8)  VALUE "returns ".
               10  COLUMN 46  PIC ZZ9   SUM WS-IN-ONE.
       PROCEDURE DIVISION.
       0000-MAIN-LINE.
           OPEN INPUT LOAN-FILE OUTPUT REPORT-FILE
           INITIATE CIRCULATION-REPORT
           PERFORM UNTIL END-OF-LOANS
               READ LOAN-FILE
                   AT END SET END-OF-LOANS TO TRUE
                   NOT AT END PERFORM 2000-GENERATE-LOAN
               END-READ
           END-PERFORM
           TERMINATE CIRCULATION-REPORT
           CLOSE LOAN-FILE REPORT-FILE
           STOP RUN.
       2000-GENERATE-LOAN.
           MOVE ZERO TO WS-OUT-ONE WS-IN-ONE
           IF LOAN-CHECKOUT
               MOVE 1 TO WS-OUT-ONE
               MOVE "checkout" TO WS-TYPE-TEXT
           ELSE
               MOVE 1 TO WS-IN-ONE
               MOVE "return" TO WS-TYPE-TEXT
           END-IF
           GENERATE DETAIL-LINE.

Compiled with cobc -x -Wall -I. rwdemo.cob (and with -std=ibm), no warnings, it writes REPORT4. The file is 30 lines long because the report page is 30 lines, so the output below stops at the last non-blank line:

CIRCULATION REPORT           PAGE  1


DATE: 2026/10/19
    000100  checkout  000002
    Member 000100    checkouts   1   returns   0
    000101  checkout  000001
    000101  return    000001
    Member 000101    checkouts   1   returns   1
  Day total          checkouts   2   returns   1

DATE: 2026/10/20
    000100  checkout  000001
    000100  return    000002
    Member 000100    checkouts   1   returns   1
    000102  checkout  000003
    Member 000102    checkouts   1   returns   0
  Day total          checkouts   2   returns   1

Grand total          checkouts   4   returns   2

The totals are the same as in the hand-coded report: 4 checkouts and 2 returns on the grand total line. What changed is where the work is:

  • No break logic. The READ loop only sets the two flags and issues GENERATE. The system compares the control fields with the previous record, prints the member and date footings in the correct order, and prints the date heading.

  • No accumulators. The SUM clause adds the flags and rolls the member sums up into the day sums and the grand sums by itself. A SUM carries over only to a higher footing of the same report.

  • The control field in a footing (SOURCE LOAN-MEMBER-ID in the member footing) shows the value of the group that ended, because the system keeps the previous value for the footing.

  • Page handling. The page heading, page counter and the page size come from the RD; the trailing blank lines are the unused lines of the 30-line page.

The layout is also easier to change: moving a column or adding a field touches one line of the report group, and adding a level means one more control name and one more footing group.

Report Writer errors are reported at compile time; the one that beginners meet most is a control field that is not in the record or in working storage, and a SOURCE or SUM operand that is not defined, both reported as an undefined name. A GENERATE that names a report instead of a detail group produces a summary report with only the footings; its use is rare and is not covered here.

Testing a report

The text a program writes is easy to compare with a file, which makes reports a good subject for regression tests: keep the expected output of a known input file, run the program, and compare with diff (a z/OS job can do the same with IEBCOMPR, ICETOOL COMPARE or SUPERC). Test the cases where a break happens at the first record, at the last record, on every record (each key different) and never (one key), and an empty input file. See Testing COBOL Programs.

Common mistakes

  • Unsorted input. A control break works only on a file in key order; a key that comes back later prints a second group with the same key.

  • Forgetting the last group. The loop ends at end of file, and the last member and date have not been printed; perform the break paragraphs once more.

  • A break on the first record. Initialize the previous key from the first record, not to zero or spaces.

  • Resetting the total before printing it. Print and add to the next level first, then clear.

  • Mixing WRITE styles on a print file. Use AFTER ADVANCING everywhere, or BEFORE everywhere.

  • Unused bytes in a print line. Initialize group items that are built in the record with MOVE SPACES, or build lines in working storage with VALUE SPACES fillers.

  • A print file with the wrong record format on z/OS. Without FBA the control character becomes data and shows on the page.

  • A LINAGE footing line outside the body. The FOOTING value must be between 1 and the body size.

  • Hard-coding the page length. Use LINAGE (or the RD page limits) so that a change of paper size is one edit.

  • Expecting the Report Writer on z/OS. Enterprise COBOL 6.5 has none; plan the hand-coded form or a precompiler.