23 min read

> "A single-screen inquiry is where you learn CICS. A multi-screen maintenance transaction with scrolling, validation, and error recovery is where you become a CICS programmer."

Chapter 30: Advanced CICS Programming

"A single-screen inquiry is where you learn CICS. A multi-screen maintenance transaction with scrolling, validation, and error recovery is where you become a CICS programmer." — James Okafor, team lead, MedClaim

In Chapter 29, you learned the fundamentals: BMS maps, SEND MAP, RECEIVE MAP, pseudo-conversational design with COMMAREA, and basic error handling with RESP/RESP2. Those foundations are enough to build simple inquiry transactions. But production CICS applications demand far more — multi-screen workflows, scrollable browse lists, asynchronous processing, and robust error recovery.

This chapter advances your CICS skills to production level. You will learn to use Temporary Storage Queues (TSQs) and Transient Data Queues (TDQs) for data management, build multi-screen transactions with browse/scroll patterns, trigger asynchronous work with the START command, use modern channel/container programming, handle abends gracefully, and integrate CICS with web services.

30.1 Temporary Storage Queues (TSQ): Your Scratch Pad

A Temporary Storage Queue is a named, CICS-managed data store that persists across task boundaries. Unlike the COMMAREA (which is limited in size and passed between consecutive tasks), a TSQ can hold large amounts of data and be accessed by any task that knows its name.

Why TSQs Matter

The COMMAREA has a practical limit of about 32 KB. For a multi-screen transaction that needs to store a browse list of 500 account records, the COMMAREA is far too small. TSQs solve this by providing a flexible, indexed storage mechanism:

  • Each item in the queue is numbered (item 1, item 2, item 3...)
  • Any item can be read directly by its number
  • Items can be added, read, updated, or the entire queue deleted
  • TSQs survive across pseudo-conversational task boundaries
  • TSQs can be in main storage (fast, volatile) or auxiliary storage (slower, recoverable)

Writing to a TSQ

       WORKING-STORAGE SECTION.
       01  WS-TSQ-NAME             PIC X(8).
       01  WS-TSQ-ITEM             PIC S9(4) COMP.
       01  WS-TSQ-DATA.
           05  WS-TSQ-ACCT-NUM    PIC X(10).
           05  WS-TSQ-ACCT-NAME   PIC X(50).
           05  WS-TSQ-BALANCE     PIC S9(11)V9(2) COMP-3.
       01  WS-TSQ-LEN             PIC S9(4) COMP VALUE 67.
       01  WS-RESP                 PIC S9(8) COMP.

       PROCEDURE DIVISION.
      *--------------------------------------------------------------
      * Build a unique queue name using terminal ID
      *--------------------------------------------------------------
           STRING 'BRW' EIBTRMID
               DELIMITED BY SIZE
               INTO WS-TSQ-NAME
           END-STRING

      *--------------------------------------------------------------
      * Write first item (WRITEQ TS creates the queue)
      *--------------------------------------------------------------
           EXEC CICS
               WRITEQ TS QUEUE(WS-TSQ-NAME)
                    FROM(WS-TSQ-DATA)
                    LENGTH(WS-TSQ-LEN)
                    ITEM(WS-TSQ-ITEM)
                    RESP(WS-RESP)
           END-EXEC

           IF WS-RESP = DFHRESP(NORMAL)
               DISPLAY 'Written item: ' WS-TSQ-ITEM
           END-IF

💡 Queue Naming Convention: Always include the terminal ID (EIBTRMID) in the TSQ name. This ensures each user has a private queue. A common pattern is: 3-character prefix + 4-character terminal ID + optional suffix. Example: BRWT001 for a browse queue on terminal T001.

Reading from a TSQ

      *--------------------------------------------------------------
      * Read a specific item by number
      *--------------------------------------------------------------
           MOVE 5 TO WS-TSQ-ITEM

           EXEC CICS
               READQ TS QUEUE(WS-TSQ-NAME)
                    INTO(WS-TSQ-DATA)
                    LENGTH(WS-TSQ-LEN)
                    ITEM(WS-TSQ-ITEM)
                    RESP(WS-RESP)
           END-EXEC

           EVALUATE WS-RESP
               WHEN DFHRESP(NORMAL)
                   PERFORM 3000-DISPLAY-ITEM
               WHEN DFHRESP(ITEMERR)
                   MOVE 'Item not found in queue' TO MSGO
               WHEN DFHRESP(QIDERR)
                   MOVE 'Queue does not exist' TO MSGO
               WHEN OTHER
                   PERFORM 9500-LOG-ERROR
           END-EVALUATE

Reading Sequentially (NEXT)

      *--------------------------------------------------------------
      * Read items sequentially
      *--------------------------------------------------------------
           EXEC CICS
               READQ TS QUEUE(WS-TSQ-NAME)
                    INTO(WS-TSQ-DATA)
                    LENGTH(WS-TSQ-LEN)
                    NEXT
                    RESP(WS-RESP)
           END-EXEC

The NEXT option reads the next item in sequence. After the last item, RESP returns ITEMERR.

Updating a TSQ Item

      *--------------------------------------------------------------
      * Update an existing item (rewrite)
      *--------------------------------------------------------------
           MOVE 3 TO WS-TSQ-ITEM

           EXEC CICS
               WRITEQ TS QUEUE(WS-TSQ-NAME)
                    FROM(WS-TSQ-DATA)
                    LENGTH(WS-TSQ-LEN)
                    ITEM(WS-TSQ-ITEM)
                    REWRITE
                    RESP(WS-RESP)
           END-EXEC

Deleting a TSQ

      *--------------------------------------------------------------
      * Delete the entire queue when done
      *--------------------------------------------------------------
           EXEC CICS
               DELETEQ TS QUEUE(WS-TSQ-NAME)
                    RESP(WS-RESP)
           END-EXEC

      *    QIDERR is OK — queue may already be deleted
           IF WS-RESP NOT = DFHRESP(NORMAL)
               AND WS-RESP NOT = DFHRESP(QIDERR)
               PERFORM 9500-LOG-ERROR
           END-IF

⚠️ Critical Rule: Always delete your TSQs when the transaction ends (PF3/exit). Orphaned TSQs consume CICS storage indefinitely. A common practice is to also attempt deletion at the start of the transaction (first invocation), in case a previous session terminated abnormally without cleanup.

TSQ Storage Options

Option Storage Survives Region Restart Use Case
MAIN Main storage No Small, temporary browse data
AUXILIARY Auxiliary storage (DASD) Yes (if recoverable) Large data sets, recovery needed
      *    Specify auxiliary storage for large queues
           EXEC CICS
               WRITEQ TS QUEUE(WS-TSQ-NAME)
                    FROM(WS-TSQ-DATA)
                    LENGTH(WS-TSQ-LEN)
                    ITEM(WS-TSQ-ITEM)
                    AUXILIARY
                    RESP(WS-RESP)
           END-EXEC

TSQ Item Size and Structure

TSQ items are variable-length — each WRITEQ TS can write a different number of bytes. However, for browse/scroll patterns, you typically use a fixed-size item structure for consistency:

       01  WS-TSQ-BROWSE-ITEM.
           05  WS-BRW-KEY          PIC X(10).
           05  WS-BRW-NAME         PIC X(30).
           05  WS-BRW-AMOUNT       PIC S9(9)V9(2) COMP-3.
           05  WS-BRW-STATUS       PIC X(8).
           05  WS-BRW-DATE         PIC X(10).
       01  WS-BRW-LEN             PIC S9(4) COMP VALUE 64.

When reading a TSQ item, the LENGTH field is both input and output. On input, it specifies the maximum length your buffer can hold. On output, it contains the actual length of the item read. If the item is longer than your buffer, you receive LENGERR:

      *    Set max length before reading
           MOVE 64 TO WS-BRW-LEN

           EXEC CICS
               READQ TS QUEUE(WS-TSQ-NAME)
                    INTO(WS-TSQ-BROWSE-ITEM)
                    LENGTH(WS-BRW-LEN)
                    ITEM(WS-ITEM-NUM)
                    RESP(WS-RESP)
           END-EXEC

      *    After read, WS-BRW-LEN contains actual item length
           IF WS-RESP = DFHRESP(LENGERR)
               DISPLAY 'Item too large for buffer'
           END-IF

TSQ Performance Considerations

Maria Chen tracks these TSQ performance guidelines at GlobalBank:

  1. Keep items small. Each TSQ read/write involves CICS storage management. Smaller items mean faster operations. For browse patterns, store only the fields needed for the list display, not the entire database record.

  2. Limit queue size. A TSQ with 10,000 items consuming 100 bytes each uses 1 MB of CICS main storage (or auxiliary storage). With 200 concurrent users, that is 200 MB. Use FETCH FIRST n ROWS ONLY to limit the data loaded.

  3. Use MAIN for small, short-lived queues. MAIN storage is faster than AUXILIARY because it avoids disk I/O. For browse queues that will exist for a few minutes and contain fewer than 500 items, MAIN is the right choice.

  4. Use AUXILIARY for large or long-lived queues. If the queue might grow large or if it needs to survive a CICS warm restart, use AUXILIARY storage.

  5. Delete queues promptly. The most common TSQ-related production problem is storage exhaustion from orphaned queues. Delete on exit and at transaction start.

🧪 Try It Yourself: Write a simple CICS program that creates a TSQ with 100 items, reads them by item number, reads them sequentially, updates item 50, and then deletes the queue. Check the RESP code after each operation and display the results. This exercise will make the TSQ API second nature.

30.2 Transient Data Queues (TDQ): Event-Driven Processing

While TSQs are scratch pads for application data, Transient Data Queues are event channels — they feed data to other processes. There are two types:

Intrapartition TDQs

Intrapartition TDQs are internal to CICS. They are commonly used for: - Trigger-level processing: When the queue reaches N items, CICS automatically starts a transaction to process them - Logging: Write audit or debug records that a monitoring transaction collects - Decoupling: Producer transactions write to the queue; consumer transactions read from it

      *--------------------------------------------------------------
      * Write an audit record to an intrapartition TDQ
      *--------------------------------------------------------------
       01  WS-AUDIT-RECORD.
           05  WS-AUD-TIMESTAMP    PIC X(26).
           05  WS-AUD-TERM-ID     PIC X(4).
           05  WS-AUD-TXN-ID      PIC X(4).
           05  WS-AUD-USER-ID     PIC X(8).
           05  WS-AUD-ACTION      PIC X(10).
           05  WS-AUD-DETAIL      PIC X(50).
       01  WS-AUD-LEN             PIC S9(4) COMP VALUE 102.

       PROCEDURE DIVISION.
           MOVE EIBTRMID TO WS-AUD-TERM-ID
           MOVE EIBTRNID TO WS-AUD-TXN-ID
           MOVE 'INQUIRY' TO WS-AUD-ACTION
           STRING 'Account lookup: ' WS-CA-ACCT-NUM
               DELIMITED BY SIZE
               INTO WS-AUD-DETAIL
           END-STRING

           EXEC CICS
               WRITEQ TD QUEUE('AUDT')
                    FROM(WS-AUDIT-RECORD)
                    LENGTH(WS-AUD-LEN)
                    RESP(WS-RESP)
           END-EXEC

Extrapartition TDQs

Extrapartition TDQs are connected to external data sets (sequential files). They provide a bridge between CICS and the batch world:

      *--------------------------------------------------------------
      * Write to an extrapartition TDQ (goes to a sequential file)
      *--------------------------------------------------------------
           EXEC CICS
               WRITEQ TD QUEUE('RPTQ')
                    FROM(WS-REPORT-LINE)
                    LENGTH(WS-RPT-LEN)
                    RESP(WS-RESP)
           END-EXEC

Reading from a TDQ

Unlike TSQs, TDQ reads are destructive — once read, the item is gone:

           EXEC CICS
               READQ TD QUEUE('AUDT')
                    INTO(WS-AUDIT-RECORD)
                    LENGTH(WS-AUD-LEN)
                    RESP(WS-RESP)
           END-EXEC

           IF WS-RESP = DFHRESP(QZERO)
      *        Queue is empty
               DISPLAY 'No more audit records'
           END-IF

Trigger-Level Processing

When you define an intrapartition TDQ with a trigger level, CICS automatically starts a specified transaction when the queue reaches that many items:

DEFINE TDQUEUE(AUDT)
       TYPE(INTRA)
       TRIGGERLEVEL(100)
       TRANSID(AUDP)

When 100 audit records accumulate in queue AUDT, CICS starts transaction AUDP to process them. This is a powerful pattern for decoupling real-time transaction processing from batch-like operations.

The trigger-level transaction should drain the queue completely in a single invocation:

      *--------------------------------------------------------------
      * Trigger-level audit processor (AUDP)
      * Started automatically when AUDT queue reaches 100 items
      *--------------------------------------------------------------
       PROCEDURE DIVISION.
       0000-MAIN.
           MOVE 0 TO WS-PROCESSED-COUNT
           MOVE 0 TO WS-ERROR-COUNT

           PERFORM UNTIL WS-RESP = DFHRESP(QZERO)
               EXEC CICS
                   READQ TD QUEUE('AUDT')
                        INTO(WS-AUDIT-RECORD)
                        LENGTH(WS-AUD-LEN)
                        RESP(WS-RESP)
               END-EXEC

               IF WS-RESP = DFHRESP(NORMAL)
                   ADD 1 TO WS-PROCESSED-COUNT
                   PERFORM 1000-WRITE-AUDIT-TO-DB2
               END-IF
           END-PERFORM

      *    Log the batch to CSMT
           STRING 'AUDP: Processed '
                  WS-PROCESSED-COUNT ' audit records, '
                  WS-ERROR-COUNT ' errors'
               DELIMITED BY '  '
               INTO WS-LOG-MSG
           END-STRING

           EXEC CICS
               WRITEQ TD QUEUE('CSMT')
                    FROM(WS-LOG-MSG)
                    LENGTH(WS-LOG-LEN)
           END-EXEC

           EXEC CICS RETURN END-EXEC.

⚠️ Trigger Level Tuning: Setting the trigger level too low (e.g., 1) causes a transaction start for every single write — high overhead. Setting it too high (e.g., 10,000) means long delays before processing begins. At MedClaim, Tomas Rivera tunes trigger levels based on production volume: 100 for the audit queue (moderate volume), 10 for the alert queue (urgent items), and 500 for the reporting queue (high volume, low urgency).

📊 TSQ vs. TDQ Decision Matrix: | Need | Use | |------|-----| | Browse/scroll data for one user | TSQ (with terminal ID in name) | | Multi-screen state beyond COMMAREA | TSQ | | Audit trail / logging | TDQ (intrapartition) | | Output to a file for batch processing | TDQ (extrapartition) | | Trigger automatic processing at threshold | TDQ (intrapartition with trigger) | | Data that must persist if CICS restarts | TSQ (auxiliary, recoverable) |

30.3 The START Command: Asynchronous Processing

The START command tells CICS to initiate a transaction at some future time or immediately but as a separate task — without waiting for it to complete. This is CICS's mechanism for asynchronous processing.

Immediate Async Start

      *--------------------------------------------------------------
      * Trigger an async notification after account update
      *--------------------------------------------------------------
       01  WS-NOTIFY-DATA.
           05  WS-NOT-ACCT-NUM    PIC X(10).
           05  WS-NOT-ACTION      PIC X(10).
           05  WS-NOT-AMOUNT      PIC S9(11)V9(2) COMP-3.
       01  WS-NOT-LEN             PIC S9(4) COMP VALUE 27.

           MOVE WS-CA-ACCT-NUM TO WS-NOT-ACCT-NUM
           MOVE 'TRANSFER' TO WS-NOT-ACTION
           MOVE WS-TRANSFER-AMT TO WS-NOT-AMOUNT

           EXEC CICS
               START TRANSID('NTFY')
                     FROM(WS-NOTIFY-DATA)
                     LENGTH(WS-NOT-LEN)
                     RESP(WS-RESP)
           END-EXEC

           IF WS-RESP = DFHRESP(NORMAL)
               MOVE 'Notification queued' TO MSGO
           ELSE
               MOVE 'Warning: notification failed' TO MSGO
           END-IF

The started transaction (NTFY) runs as a completely separate task. It retrieves the data with RETRIEVE:

      *--------------------------------------------------------------
      * In the NTFY transaction — retrieve data from START
      *--------------------------------------------------------------
       PROCEDURE DIVISION.
           EXEC CICS
               RETRIEVE INTO(WS-NOTIFY-DATA)
                        LENGTH(WS-NOT-LEN)
                        RESP(WS-RESP)
           END-EXEC

           IF WS-RESP = DFHRESP(NORMAL)
               PERFORM 1000-SEND-NOTIFICATION
           END-IF
           EXEC CICS RETURN END-EXEC.

Delayed Start (Timer)

      *--------------------------------------------------------------
      * Start a cleanup transaction in 30 minutes
      *--------------------------------------------------------------
           EXEC CICS
               START TRANSID('CLNP')
                     FROM(WS-CLEANUP-DATA)
                     LENGTH(WS-CLNP-LEN)
                     INTERVAL(003000)
                     RESP(WS-RESP)
           END-EXEC
      *    INTERVAL(003000) = 00 hours, 30 minutes, 00 seconds

Start at a Specific Time

      *--------------------------------------------------------------
      * Start end-of-day processing at 5:00 PM
      *--------------------------------------------------------------
           EXEC CICS
               START TRANSID('EODP')
                     FROM(WS-EOD-DATA)
                     LENGTH(WS-EOD-LEN)
                     TIME(170000)
                     RESP(WS-RESP)
           END-EXEC
      *    TIME(170000) = 17:00:00 (5:00 PM)

💡 Async Pattern: The START command is CICS's equivalent of a message queue or event trigger. Use it when the user's transaction should not wait for a secondary operation to complete — sending emails, generating reports, triggering downstream processing, or scheduling cleanup.

We introduced LINK and XCTL in Chapter 29. Now let us explore them in depth.

LINK calls a program and waits for it to return. The COMMAREA is the data exchange mechanism:

      *--------------------------------------------------------------
      * Call a validation subroutine via LINK
      *--------------------------------------------------------------
       01  WS-VALIDATION-AREA.
           05  WS-VAL-ACCT-NUM    PIC X(10).
           05  WS-VAL-AMOUNT      PIC S9(11)V9(2) COMP-3.
           05  WS-VAL-TXN-TYPE    PIC X(2).
           05  WS-VAL-RETURN-CODE PIC S9(4) COMP.
           05  WS-VAL-RETURN-MSG  PIC X(50).
       01  WS-VAL-LEN             PIC S9(4) COMP VALUE 71.

           MOVE WS-CA-ACCT-NUM TO WS-VAL-ACCT-NUM
           MOVE WS-TXN-AMOUNT TO WS-VAL-AMOUNT
           MOVE 'DR' TO WS-VAL-TXN-TYPE

           EXEC CICS
               LINK PROGRAM('TXNVALID')
                    COMMAREA(WS-VALIDATION-AREA)
                    LENGTH(WS-VAL-LEN)
                    RESP(WS-RESP)
           END-EXEC

           EVALUATE WS-RESP
               WHEN DFHRESP(NORMAL)
                   EVALUATE WS-VAL-RETURN-CODE
                       WHEN 0
                           PERFORM 4000-PROCESS-TXN
                       WHEN 1
                           MOVE WS-VAL-RETURN-MSG TO MSGO
                           PERFORM 1500-SEND-DATAONLY
                       WHEN OTHER
                           PERFORM 9500-LOG-ERROR
                   END-EVALUATE
               WHEN DFHRESP(PGMIDERR)
                   MOVE 'Validation program not found'
                       TO MSGO
                   PERFORM 1500-SEND-DATAONLY
               WHEN OTHER
                   PERFORM 9500-LOG-ERROR
           END-EVALUATE

XCTL for Menu Navigation

XCTL transfers control permanently — the calling program's storage is freed:

      *--------------------------------------------------------------
      * Transfer to account maintenance from the menu
      *--------------------------------------------------------------
           EXEC CICS
               XCTL PROGRAM('ACCTMNT')
                    COMMAREA(WS-MENU-DATA)
                    LENGTH(WS-MENU-LEN)
                    RESP(WS-RESP)
           END-EXEC

      *    If we get here, XCTL failed
           IF WS-RESP = DFHRESP(PGMIDERR)
               MOVE 'Maintenance program not available'
                   TO MSGO
               PERFORM 1500-SEND-DATAONLY
           END-IF

When to Use Each

Scenario Use Why
Call a validation routine LINK Need control back to continue processing
Call a logging utility LINK Need control back
Navigate from menu to a functional program XCTL No need to return to menu program
Chain from one transaction module to another XCTL Previous module's work is done
Call a stored procedure wrapper LINK Need the result

30.5 Channel/Container Programming: The Modern Approach

COMMAREA has limitations: a maximum practical size of about 32 KB and a flat data structure. Modern CICS introduces channels and containers as a more flexible data exchange mechanism.

A channel is a named collection of containers. Each container holds a named piece of data. Think of it as passing a named dictionary (or map) between programs instead of a single flat buffer.

Creating and Populating Containers

      *--------------------------------------------------------------
      * Put data into containers on a channel
      *--------------------------------------------------------------
       01  WS-CHANNEL-NAME         PIC X(16) VALUE 'ACCT-CHANNEL'.
       01  WS-ACCT-DATA.
           05  WS-CN-ACCT-NUM     PIC X(10).
           05  WS-CN-ACCT-NAME    PIC X(50).
           05  WS-CN-BALANCE      PIC S9(11)V9(2) COMP-3.
       01  WS-ACCT-LEN            PIC S9(8) COMP VALUE 67.

       01  WS-TXN-LIST.
           05  WS-CN-TXN OCCURS 10 TIMES.
               10  WS-CN-TXN-ID   PIC X(15).
               10  WS-CN-TXN-AMT  PIC S9(11)V9(2) COMP-3.
               10  WS-CN-TXN-DATE PIC X(10).
       01  WS-TXN-LEN             PIC S9(8) COMP VALUE 320.

       01  WS-ERROR-MSG            PIC X(80).
       01  WS-ERR-LEN              PIC S9(8) COMP VALUE 80.

       PROCEDURE DIVISION.
      *    Put account data in one container
           EXEC CICS
               PUT CONTAINER('ACCT-DATA')
                   CHANNEL(WS-CHANNEL-NAME)
                   FROM(WS-ACCT-DATA)
                   FLENGTH(WS-ACCT-LEN)
                   RESP(WS-RESP)
           END-EXEC

      *    Put transaction list in another container
           EXEC CICS
               PUT CONTAINER('TXN-LIST')
                   CHANNEL(WS-CHANNEL-NAME)
                   FROM(WS-TXN-LIST)
                   FLENGTH(WS-TXN-LEN)
                   RESP(WS-RESP)
           END-EXEC

      *    LINK with channel instead of COMMAREA
           EXEC CICS
               LINK PROGRAM('ACCTPROC')
                    CHANNEL(WS-CHANNEL-NAME)
                    RESP(WS-RESP)
           END-EXEC

      *    Read back results
           EXEC CICS
               GET CONTAINER('ERROR-MSG')
                   CHANNEL(WS-CHANNEL-NAME)
                   INTO(WS-ERROR-MSG)
                   FLENGTH(WS-ERR-LEN)
                   RESP(WS-RESP)
           END-EXEC

           IF WS-RESP = DFHRESP(CONTAINERERR)
      *        Container not found — no error occurred
               CONTINUE
           END-IF

Advantages of Channels/Containers Over COMMAREA

Feature COMMAREA Channels/Containers
Maximum size ~32 KB practical No practical limit
Structure Flat, single buffer Named containers, multiple data items
Optional data Must include all fields Containers can be absent (CONTAINERERR)
Type safety None Can specify CHAR or BIT data types
Web service friendly Requires manual mapping Can map directly to JSON/XML

🔗 Connection to Web Services: Channels and containers are the native data model for CICS web services. When CICS exposes a program as a web service, the JSON or XML request body maps directly to containers. When you design programs using channels/containers from the start, you make them web-service-ready with minimal changes.

Migration Strategy: COMMAREA to Channels

Most existing CICS applications use COMMAREA. Converting to channels/containers is a gradual process. Derek Washington uses this migration approach at GlobalBank:

Phase 1: New programs use channels. Any new LINK or XCTL call uses the channel/container model. Existing programs continue with COMMAREA.

Phase 2: Create wrapper programs. For existing programs that cannot be immediately changed, write thin wrapper programs that accept a channel, extract the container data into a local COMMAREA-format working storage area, and LINK to the original program with that COMMAREA:

      *--------------------------------------------------------------
      * Wrapper: accepts channel, calls COMMAREA-based program
      *--------------------------------------------------------------
       PROCEDURE DIVISION.
      *    Read the channel container
           EXEC CICS
               GET CONTAINER('REQUEST')
                   CHANNEL('ACCT-CHANNEL')
                   INTO(WS-COMMAREA)
                   FLENGTH(WS-COMM-LEN)
                   RESP(WS-RESP)
           END-EXEC

      *    Call the legacy program with COMMAREA
           EXEC CICS
               LINK PROGRAM('OLDPROG')
                    COMMAREA(WS-COMMAREA)
                    LENGTH(WS-COMM-LEN)
                    RESP(WS-RESP)
           END-EXEC

      *    Put the response back in a container
           EXEC CICS
               PUT CONTAINER('RESPONSE')
                   CHANNEL('ACCT-CHANNEL')
                   FROM(WS-COMMAREA)
                   FLENGTH(WS-COMM-LEN)
           END-EXEC

           EXEC CICS RETURN END-EXEC.

Phase 3: Convert high-volume programs. Gradually convert the most frequently called programs to accept channels natively, removing the wrapper overhead.

This phased approach avoids a risky "big bang" conversion while moving steadily toward the modern programming model.

The Called Program Reads Containers

      *--------------------------------------------------------------
      * In the called program — read containers from the channel
      *--------------------------------------------------------------
       IDENTIFICATION DIVISION.
       PROGRAM-ID. ACCTPROC.

       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  WS-ACCT-DATA.
           05  WS-CN-ACCT-NUM     PIC X(10).
           05  WS-CN-ACCT-NAME    PIC X(50).
           05  WS-CN-BALANCE      PIC S9(11)V9(2) COMP-3.
       01  WS-ACCT-LEN            PIC S9(8) COMP VALUE 67.
       01  WS-RESP                 PIC S9(8) COMP.

       PROCEDURE DIVISION.
      *    Get the current channel name
           EXEC CICS
               ASSIGN CHANNEL(WS-CURRENT-CHANNEL)
           END-EXEC

      *    Read account data container
           EXEC CICS
               GET CONTAINER('ACCT-DATA')
                   CHANNEL(WS-CURRENT-CHANNEL)
                   INTO(WS-ACCT-DATA)
                   FLENGTH(WS-ACCT-LEN)
                   RESP(WS-RESP)
           END-EXEC

           IF WS-RESP = DFHRESP(NORMAL)
               PERFORM 1000-PROCESS-ACCOUNT
           END-IF

      *    Put result back in a container
           EXEC CICS
               PUT CONTAINER('RESULT')
                   CHANNEL(WS-CURRENT-CHANNEL)
                   FROM(WS-RESULT-DATA)
                   FLENGTH(WS-RESULT-LEN)
           END-EXEC

           EXEC CICS RETURN END-EXEC.

30.6 Multi-Screen Transactions: Browse/Scroll Pattern

One of the most common CICS patterns is the browse/scroll screen — displaying a list of records that the user can page through with PF7 (backward) and PF8 (forward). This pattern combines TSQs, COMMAREA state management, and careful cursor positioning.

Architecture of a Browse Transaction

1. User enters search criteria → Query DB2 → Store results in TSQ
2. Display first page (items 1-15) from TSQ
3. User presses PF8 → Display items 16-30 from TSQ
4. User presses PF7 → Display items 1-15 from TSQ
5. User selects a row → Display detail screen
6. User presses PF3 from detail → Return to browse
7. User presses PF3 from browse → Clean up TSQ, exit

COMMAREA for Browse State

       01  WS-COMMAREA.
           05  WS-CA-STATE         PIC X(1).
               88  WS-CA-SEARCH            VALUE 'S'.
               88  WS-CA-BROWSE            VALUE 'B'.
               88  WS-CA-DETAIL            VALUE 'D'.
           05  WS-CA-TSQ-NAME     PIC X(8).
           05  WS-CA-TOTAL-ITEMS  PIC S9(4) COMP.
           05  WS-CA-TOP-ITEM     PIC S9(4) COMP.
           05  WS-CA-PAGE-SIZE    PIC S9(4) COMP VALUE 15.
           05  WS-CA-SEARCH-KEY   PIC X(10).
           05  WS-CA-SELECTED-KEY PIC X(10).
       01  WS-COMMAREA-LEN        PIC S9(4) COMP VALUE 30.

Loading Data into the TSQ

       2000-LOAD-BROWSE-DATA.
      *--------------------------------------------------------------
      * Query DB2 and store results in a TSQ
      *--------------------------------------------------------------
      *    Delete any existing queue from previous session
           EXEC CICS
               DELETEQ TS QUEUE(WS-CA-TSQ-NAME)
                    RESP(WS-RESP)
           END-EXEC

      *    Open cursor for search results
           EXEC SQL
               DECLARE BROWSE-CURSOR CURSOR FOR
                   SELECT ACCT_NUMBER, ACCT_NAME,
                          ACCT_BALANCE, ACCT_STATUS
                   FROM ACCOUNT
                   WHERE BRANCH_CODE = :WS-CA-SEARCH-KEY
                   ORDER BY ACCT_NAME
                   FETCH FIRST 500 ROWS ONLY
           END-EXEC

           EXEC SQL OPEN BROWSE-CURSOR END-EXEC

           MOVE 0 TO WS-CA-TOTAL-ITEMS
           PERFORM UNTIL SQLCODE = +100
               EXEC SQL
                   FETCH BROWSE-CURSOR
                   INTO :WS-TSQ-ACCT-NUM,
                        :WS-TSQ-ACCT-NAME,
                        :WS-TSQ-BALANCE,
                        :WS-TSQ-STATUS
               END-EXEC

               IF SQLCODE = 0
                   ADD 1 TO WS-CA-TOTAL-ITEMS
                   EXEC CICS
                       WRITEQ TS QUEUE(WS-CA-TSQ-NAME)
                            FROM(WS-TSQ-DATA)
                            LENGTH(WS-TSQ-LEN)
                            ITEM(WS-TSQ-ITEM)
                            MAIN
                            RESP(WS-RESP)
                   END-EXEC
               END-IF
           END-PERFORM

           EXEC SQL CLOSE BROWSE-CURSOR END-EXEC

           IF WS-CA-TOTAL-ITEMS = 0
               MOVE 'No accounts found for this branch'
                   TO MSGO
               PERFORM 1500-SEND-SEARCH-DATAONLY
           ELSE
               MOVE 1 TO WS-CA-TOP-ITEM
               PERFORM 3000-DISPLAY-BROWSE-PAGE
           END-IF.

Displaying a Page from the TSQ

       3000-DISPLAY-BROWSE-PAGE.
      *--------------------------------------------------------------
      * Display one page of browse data from the TSQ
      *--------------------------------------------------------------
           INITIALIZE ACCTBRWO

      *    Calculate page bounds
           COMPUTE WS-LAST-ITEM =
               WS-CA-TOP-ITEM + WS-CA-PAGE-SIZE - 1
           IF WS-LAST-ITEM > WS-CA-TOTAL-ITEMS
               MOVE WS-CA-TOTAL-ITEMS TO WS-LAST-ITEM
           END-IF

      *    Read items from TSQ and populate map rows
           MOVE 0 TO WS-ROW-IDX
           PERFORM VARYING WS-TSQ-ITEM
               FROM WS-CA-TOP-ITEM BY 1
               UNTIL WS-TSQ-ITEM > WS-LAST-ITEM

               EXEC CICS
                   READQ TS QUEUE(WS-CA-TSQ-NAME)
                        INTO(WS-TSQ-DATA)
                        LENGTH(WS-TSQ-LEN)
                        ITEM(WS-TSQ-ITEM)
                        RESP(WS-RESP)
               END-EXEC

               IF WS-RESP = DFHRESP(NORMAL)
                   ADD 1 TO WS-ROW-IDX
                   MOVE WS-TSQ-ACCT-NUM
                       TO WS-BRW-ACCT(WS-ROW-IDX)
                   MOVE WS-TSQ-ACCT-NAME
                       TO WS-BRW-NAME(WS-ROW-IDX)
                   MOVE WS-TSQ-BALANCE
                       TO WS-BRW-BAL-FMT
                   MOVE WS-BRW-BAL-FMT
                       TO WS-BRW-BAL(WS-ROW-IDX)
                   MOVE WS-TSQ-STATUS
                       TO WS-BRW-STAT(WS-ROW-IDX)
               END-IF
           END-PERFORM

      *    Set status message
           STRING 'Items '
                  WS-CA-TOP-ITEM '-' WS-LAST-ITEM
                  ' of ' WS-CA-TOTAL-ITEMS
                  '  PF7=Back PF8=Forward PF3=Exit'
               DELIMITED BY '  '
               INTO MSGO
           END-STRING

           MOVE 'B' TO WS-CA-STATE
           PERFORM 1500-SEND-BROWSE-DATAONLY.

Handling Scroll Keys

       4000-PROCESS-BROWSE-INPUT.
      *--------------------------------------------------------------
      * Handle PF7/PF8 scrolling and row selection
      *--------------------------------------------------------------
           EVALUATE EIBAID
               WHEN DFHPF7
      *            Scroll backward
                   COMPUTE WS-CA-TOP-ITEM =
                       WS-CA-TOP-ITEM - WS-CA-PAGE-SIZE
                   IF WS-CA-TOP-ITEM < 1
                       MOVE 1 TO WS-CA-TOP-ITEM
                       MOVE 'Already at top of list' TO MSGO
                   END-IF
                   PERFORM 3000-DISPLAY-BROWSE-PAGE

               WHEN DFHPF8
      *            Scroll forward
                   COMPUTE WS-NEW-TOP =
                       WS-CA-TOP-ITEM + WS-CA-PAGE-SIZE
                   IF WS-NEW-TOP > WS-CA-TOTAL-ITEMS
                       MOVE 'Already at end of list' TO MSGO
                       PERFORM 3000-DISPLAY-BROWSE-PAGE
                   ELSE
                       MOVE WS-NEW-TOP TO WS-CA-TOP-ITEM
                       PERFORM 3000-DISPLAY-BROWSE-PAGE
                   END-IF

               WHEN DFHENTER
      *            User selected a row — check selection field
                   PERFORM 4100-PROCESS-SELECTION

               WHEN DFHPF3
      *            Exit — clean up TSQ
                   EXEC CICS
                       DELETEQ TS QUEUE(WS-CA-TSQ-NAME)
                            RESP(WS-RESP)
                   END-EXEC
                   EXEC CICS RETURN END-EXEC

               WHEN OTHER
                   MOVE 'PF7=Back PF8=Fwd Enter=Select PF3=Exit'
                       TO MSGO
                   PERFORM 3000-DISPLAY-BROWSE-PAGE
           END-EVALUATE.

🧪 Try It Yourself: Design a BMS map for the browse screen with 15 repeating rows. Each row should have: a 1-character selection field (UNPROT), account number, account name, balance, and status. The selection field is where the user types 'S' to select a record.

Processing Row Selections

When the user types 'S' next to a row and presses Enter, the program needs to determine which row was selected. This requires scanning the selection fields in the received map:

       4100-PROCESS-SELECTION.
      *--------------------------------------------------------------
      * Scan selection fields for 'S' and show detail
      *--------------------------------------------------------------
           EXEC CICS
               RECEIVE MAP('ACCTBRW')
                       MAPSET('BRWSET')
                       INTO(ACCTBRWI)
                       RESP(WS-RESP)
           END-EXEC

           IF WS-RESP = DFHRESP(MAPFAIL)
               MOVE 'Type S next to a row, press Enter'
                   TO MSGO
               PERFORM 3000-DISPLAY-BROWSE-PAGE
               EXIT PARAGRAPH
           END-IF

      *    Scan through the 15 selection fields
           MOVE 0 TO WS-SELECTED-ROW
           PERFORM VARYING WS-SCAN-IDX FROM 1 BY 1
               UNTIL WS-SCAN-IDX > 15
                  OR WS-SELECTED-ROW > 0

               IF BRW-SELL(WS-SCAN-IDX) > 0
                   IF BRW-SELI(WS-SCAN-IDX) = 'S'
                       OR BRW-SELI(WS-SCAN-IDX) = 's'
                       MOVE WS-SCAN-IDX TO WS-SELECTED-ROW
                   ELSE
      *                Invalid selection character
                       MOVE 'Type S to select (not '
                         &  BRW-SELI(WS-SCAN-IDX)
                         &  ')' TO MSGO
                       PERFORM 3000-DISPLAY-BROWSE-PAGE
                       EXIT PARAGRAPH
                   END-IF
               END-IF
           END-PERFORM

           IF WS-SELECTED-ROW = 0
               MOVE 'No row selected. Type S to select.'
                   TO MSGO
               PERFORM 3000-DISPLAY-BROWSE-PAGE
           ELSE
      *        Calculate TSQ item number
               COMPUTE WS-SELECTED-ITEM =
                   WS-CA-TOP-ITEM + WS-SELECTED-ROW - 1
               PERFORM 5000-SHOW-DETAIL
           END-IF.

This pattern — scanning repeating fields for a selection marker — is one of the most common patterns in production CICS programs. The key points:

  1. Accept both uppercase and lowercase ('S' and 's') — users may have Caps Lock off
  2. Reject invalid selection characters rather than ignoring them — provide feedback
  3. Handle the case where no selection is found — the user pressed Enter without typing 'S' anywhere
  4. Calculate the TSQ item number from the screen row position: item = top-item + selected-row - 1

The Detail-to-Browse Return Pattern

When the user views a detail record and presses PF3 to return to the browse, the browse page must redisplay at the same position it was in before the detail was shown. This is why the COMMAREA stores WS-CA-TOP-ITEM — the browse redisplay reads from the TSQ starting at the saved position, giving the user a seamless experience:

       6000-RETURN-TO-BROWSE.
      *    State returns to 'B' (browse)
      *    WS-CA-TOP-ITEM is unchanged from when we left
           MOVE 'B' TO WS-CA-STATE
           PERFORM 3000-DISPLAY-BROWSE-PAGE
      *    User sees the same page they were on before

If the TSQ has been deleted (perhaps by a cleanup transaction while the user was viewing the detail), handle the error gracefully by re-executing the search:

      *    Check if TSQ still exists
           EXEC CICS
               READQ TS QUEUE(WS-CA-TSQ-NAME)
                    INTO(WS-TSQ-DATA)
                    LENGTH(WS-TSQ-LEN)
                    ITEM(1)
                    RESP(WS-RESP)
           END-EXEC

           IF WS-RESP = DFHRESP(QIDERR)
      *        TSQ was cleaned up — reload
               MOVE 'Data expired. Reloading...' TO MSGO
               PERFORM 2000-LOAD-BROWSE-DATA
           ELSE
               PERFORM 3000-DISPLAY-BROWSE-PAGE
           END-IF

Browse Map Design with Repeating Groups

The BMS map for a browse screen uses a repeating pattern of fields. Here is a partial example showing three of the fifteen rows:

* --- Row 5: Column Headers ---
         DFHMDF POS=(5,2),                                     X
               LENGTH=60,                                      X
               ATTRB=(ASKIP,NORM),                             X
               INITIAL='S  Account    Name                      X
                     Balance     Status'
*
* --- Row 6: First data row ---
SEL01    DFHMDF POS=(6,2),                                     X
               LENGTH=1,                                       X
               ATTRB=(UNPROT,NORM,IC)
         DFHMDF POS=(6,4),LENGTH=1,ATTRB=ASKIP
ACCT01   DFHMDF POS=(6,5),                                    X
               LENGTH=10,                                      X
               ATTRB=(ASKIP,BRT)
NAME01   DFHMDF POS=(6,16),                                   X
               LENGTH=25,                                      X
               ATTRB=(ASKIP,BRT)
BAL01    DFHMDF POS=(6,42),                                   X
               LENGTH=13,                                      X
               ATTRB=(ASKIP,BRT)
STAT01   DFHMDF POS=(6,56),                                   X
               LENGTH=8,                                       X
               ATTRB=(ASKIP,BRT)
*
* --- Row 7: Second data row (same pattern) ---
SEL02    DFHMDF POS=(7,2),LENGTH=1,ATTRB=(UNPROT,NORM)
         DFHMDF POS=(7,4),LENGTH=1,ATTRB=ASKIP
ACCT02   DFHMDF POS=(7,5),LENGTH=10,ATTRB=(ASKIP,BRT)
NAME02   DFHMDF POS=(7,16),LENGTH=25,ATTRB=(ASKIP,BRT)
BAL02    DFHMDF POS=(7,42),LENGTH=13,ATTRB=(ASKIP,BRT)
STAT02   DFHMDF POS=(7,56),LENGTH=8,ATTRB=(ASKIP,BRT)

The generated symbolic map creates arrays for the repeating fields, which is why we can use subscripts like BRW-SELI(WS-SCAN-IDX) to iterate through the rows programmatically.

📊 Page Size Trade-offs: A larger page size (20 rows) means fewer PF8 presses but a denser screen. A smaller page size (10 rows) means more white space but more scrolling. The standard 3270 screen has 24 rows; after title (row 1), column headers (row 5), message line (row 22), and function keys (row 23-24), you have approximately 15 usable rows for data. This is why 15 rows per page is the most common browse page size.

30.7 HANDLE ABEND: Error Recovery in CICS

When a CICS task encounters a fatal error (division by zero, invalid address, DB2 timeout), CICS raises an abend. Without explicit handling, the task terminates and the user sees an error message. The HANDLE ABEND command lets you intercept abends and take recovery action.

Setting Up Abend Handling

       PROCEDURE DIVISION.
       0000-MAIN.
      *--------------------------------------------------------------
      * Establish abend handler first
      *--------------------------------------------------------------
           EXEC CICS
               HANDLE ABEND PROGRAM('ABERRHDL')
           END-EXEC

      *    Or handle within the same program:
           EXEC CICS
               HANDLE ABEND LABEL(9900-ABEND-HANDLER)
           END-EXEC

      *    ... normal processing ...

       9900-ABEND-HANDLER.
      *--------------------------------------------------------------
      * Abend recovery — clean up and notify user
      *--------------------------------------------------------------
      *    Log the error
           STRING 'ABEND in ' EIBTRNID
                  ' at term ' EIBTRMID
                  ' task ' EIBTASKN
               DELIMITED BY SIZE
               INTO WS-LOG-MSG
           END-STRING

           EXEC CICS
               WRITEQ TD QUEUE('CSMT')
                    FROM(WS-LOG-MSG)
                    LENGTH(WS-LOG-LEN)
                    RESP(WS-RESP)
           END-EXEC

      *    Clean up TSQ if it exists
           EXEC CICS
               DELETEQ TS QUEUE(WS-CA-TSQ-NAME)
                    RESP(WS-RESP)
           END-EXEC

      *    Notify the user
           MOVE 'A system error occurred. Please try again.'
               TO MSGO

           EXEC CICS
               SEND MAP('ERRMSG')
                    MAPSET('ERRSET')
                    FROM(ERRMSGO)
                    ERASE
                    RESP(WS-RESP)
           END-EXEC

           EXEC CICS RETURN END-EXEC.

HANDLE ABEND Scope

The HANDLE ABEND command is scoped to the current program level. If program A LINKs to program B, and program B abends, program B's HANDLE ABEND (if any) gets control first. If program B does not have a HANDLE ABEND, the abend propagates up to program A's handler. This stack-like behavior is consistent with the LINK/RETURN program model. Note that XCTL does not preserve the calling program's abend handler — since XCTL replaces the current program, the old handler is lost.

You can also temporarily disable abend handling and re-enable it:

      *    Disable abend handling (let CICS handle it)
           EXEC CICS HANDLE ABEND CANCEL END-EXEC

      *    Re-enable with the original handler
           EXEC CICS HANDLE ABEND LABEL(9900-ABEND-HANDLER)
               END-EXEC

Programmatic ABEND

Sometimes you need to intentionally abend a task — for example, when a critical data integrity condition is violated:

      *    Intentional abend with a meaningful code
           EXEC CICS ABEND ABCODE('DINT') END-EXEC
      *    DINT = Data INTegrity violation

Abend codes are 4 characters. Use a consistent naming scheme so operations staff can quickly identify the source and nature of the problem.

⚠️ Abend Code Conventions: Codes starting with 'A' are reserved by CICS (e.g., ASRA = program check, AEY7 = security violation). Use codes starting with a letter that identifies your application: 'G' for GlobalBank, 'M' for MedClaim, etc.

Understanding Common CICS Abend Codes

Every CICS developer must recognize these abend codes on sight — they are the most common causes of production incidents:

Code Name Cause Action
ASRA Program Check Division by zero, subscript out of range, invalid address Check COBOL program logic, verify array bounds, check COMMAREA access when EIBCALEN=0
AICA Runaway Task Program in an infinite loop; CICS purges it after ICVR seconds Check PERFORM loops for missing termination conditions
AKCT Task Purge Task exceeded its transaction time limit Optimize the program or increase DTIMOUT for the transaction
AEI0 Program Not Found LINK or XCTL to a program that is not defined or not available Check program name spelling, verify CSD definition, check NEWCOPY status
AEI9 Map Not Found SEND MAP with a map or mapset that is not installed Verify mapset is defined and installed in CICS
AEY7 Security Check User not authorized for the resource Check RACF definitions for the user and resource
AEIO I/O Error Unrecoverable I/O error on a file or queue Check file status, verify VSAM dataset health

Transaction Dump Analysis

When a CICS program abends without a HANDLE ABEND, CICS produces a transaction dump. The dump contains: - The program's storage at the time of the abend - The EIB contents - The COMMAREA contents - The register contents (for determining the failing instruction)

The dump is written to the CICS dump dataset and can be analyzed with the IPCS (Interactive Problem Control System) utility or CICS Explorer. The most critical piece of information is the offset of the failing instruction, which you can map to a source line using the compiler listing.

ASRA ABEND at offset X'0002B4' in program ACCTINQ

To find the failing line: 1. Open the compiler listing for ACCTINQ 2. Locate the Procedure Division Map section 3. Find offset 2B4 (hex) in the generated code 4. Map it back to the COBOL source line number

💡 Debugging Tip: Always compile with LIST and MAP options so that the compiler listing includes the Procedure Division Map. Without this map, translating an abend offset to a source line is extremely difficult. Production compilations should always include these options.

30.8 CICS Web Services: Bridging Old and New

Modern CICS allows your COBOL programs to serve as web service providers (receiving HTTP requests) or consumers (making HTTP requests). This is the primary mechanism for integrating mainframe COBOL with modern architectures.

CICS as a Web Service Provider

Using CICS's built-in pipeline infrastructure, a COBOL program can receive JSON requests and return JSON responses:

      *--------------------------------------------------------------
      * COBOL program exposed as a REST service
      * URL: /globalbank/api/v1/accounts/{acctNum}
      * Method: GET
      * Response: JSON with account details
      *--------------------------------------------------------------
       PROCEDURE DIVISION.
      *    CICS receives the HTTP request and puts data in containers

      *    Read the account number from the request
           EXEC CICS
               GET CONTAINER('DFHWS-URI-PATHINFO')
                   CHANNEL('DFHWS-DATA')
                   INTO(WS-URI-PATH)
                   FLENGTH(WS-PATH-LEN)
                   RESP(WS-RESP)
           END-EXEC

      *    Extract account number from path
           PERFORM 1000-PARSE-ACCT-FROM-PATH

      *    Look up account (same DB2 logic as terminal program)
           PERFORM 2000-LOOKUP-ACCOUNT

      *    Build JSON response
           STRING '{"accountNumber":"' WS-ACCT-NUM '",'
                  '"accountName":"' WS-ACCT-NAME '",'
                  '"balance":' WS-BALANCE-STR ','
                  '"status":"' WS-STATUS-DESC '"}'
               DELIMITED BY '  '
               INTO WS-JSON-RESPONSE
           END-STRING

      *    Put response in container
           EXEC CICS
               PUT CONTAINER('DFHWS-BODY')
                   CHANNEL('DFHWS-DATA')
                   FROM(WS-JSON-RESPONSE)
                   FLENGTH(WS-JSON-LEN)
                   CHAR
           END-EXEC

           EXEC CICS RETURN END-EXEC.

CICS as a Web Service Consumer

      *--------------------------------------------------------------
      * Call an external REST API from CICS
      *--------------------------------------------------------------
       01  WS-URL                  PIC X(200).
       01  WS-URL-LEN             PIC S9(8) COMP.
       01  WS-HTTP-RESP           PIC X(2000).
       01  WS-HTTP-RESP-LEN       PIC S9(8) COMP VALUE 2000.
       01  WS-HTTP-STATUS         PIC S9(8) COMP.
       01  WS-SESS-TOKEN          PIC X(8).

       PROCEDURE DIVISION.
           MOVE 'https://api.creditcheck.com/v1/score/'
               TO WS-URL
           STRING WS-URL WS-CUSTOMER-ID
               DELIMITED BY SPACES
               INTO WS-URL
           END-STRING
           MOVE FUNCTION LENGTH(FUNCTION TRIM(WS-URL))
               TO WS-URL-LEN

      *    Open an HTTP connection
           EXEC CICS WEB OPEN
               HOST('api.creditcheck.com')
               PORTNUMBER(443)
               SCHEME(HTTPS)
               SESSTOKEN(WS-SESS-TOKEN)
               RESP(WS-RESP)
           END-EXEC

      *    Send the request
           EXEC CICS WEB SEND
               SESSTOKEN(WS-SESS-TOKEN)
               GET
               PATH(WS-URL)
               PATHLENGTH(WS-URL-LEN)
               RESP(WS-RESP)
           END-EXEC

      *    Receive the response
           EXEC CICS WEB RECEIVE
               SESSTOKEN(WS-SESS-TOKEN)
               INTO(WS-HTTP-RESP)
               LENGTH(WS-HTTP-RESP-LEN)
               STATUSCODE(WS-HTTP-STATUS)
               RESP(WS-RESP)
           END-EXEC

      *    Close the connection
           EXEC CICS WEB CLOSE
               SESSTOKEN(WS-SESS-TOKEN)
               RESP(WS-RESP)
           END-EXEC

           IF WS-HTTP-STATUS = 200
               PERFORM 5000-PARSE-CREDIT-RESPONSE
           ELSE
               MOVE 'Credit check unavailable' TO MSGO
           END-IF.

⚖️ The Modernization Spectrum: Web service integration represents the sweet spot on the modernization spectrum. The core COBOL business logic remains unchanged and battle-tested. Only the interface layer is modernized. This approach delivers immediate value (mobile access, partner integration, cloud connectivity) with minimal risk to the underlying systems. Maria Chen calls it "evolution, not revolution."

JSON Transformation with CICS JSON Assist

Building JSON strings with STRING statements (as shown above) works for simple responses, but it is fragile and error-prone for complex data structures. CICS TS V5.2+ provides JSON Assist, which automatically generates transformation code between COBOL copybooks and JSON.

The process works as follows:

  1. Define a COBOL copybook with your data structure
  2. Run the DFHJS2LS utility (JSON Schema to Language Structure) to generate a JSON binding file
  3. Deploy the binding as a BUNDLE resource in CICS
  4. CICS handles the transformation automatically — incoming JSON is parsed into COBOL fields, and outgoing COBOL fields are serialized to JSON
      *--------------------------------------------------------------
      * Copybook: ACCTRESP — defines the JSON response structure
      * DFHJS2LS generates the binding automatically
      *--------------------------------------------------------------
       01  ACCT-RESPONSE.
           05  ACCT-NUM               PIC X(10).
           05  ACCT-NAME              PIC X(50).
           05  ACCT-TYPE              PIC X(2).
           05  ACCT-BALANCE           PIC S9(11)V9(2) COMP-3.
           05  ACCT-STATUS            PIC X(1).
           05  BRANCH-CODE            PIC X(4).
           05  LAST-ACTIVITY          PIC X(26).

With JSON Assist, the COBOL program simply populates the copybook fields. CICS converts them to JSON automatically:

{
  "accountNumber": "1234567890",
  "accountName": "GlobalBank Checking",
  "accountType": "CH",
  "accountBalance": 15234.50,
  "accountStatus": "A",
  "branchCode": "0042",
  "lastActivity": "2024-11-15-10.30.45.123456"
}

This eliminates the tedious and error-prone STRING-based JSON construction.

Pipeline Configuration

CICS web services use a pipeline — a series of processing steps that handle message transformation, security, and routing. The pipeline is defined in an XML configuration file:

<!-- Provider pipeline: incoming HTTP to COBOL -->
<provider_pipeline>
  <transport>
    <default_transport_handler/>
  </transport>
  <service>
    <service_handler_list>
      <cics_soap_1.1_handler/>
    </service_handler_list>
    <terminal_handler>
      <cics_json_handler/>
    </terminal_handler>
  </service>
</provider_pipeline>

Maria Chen notes that pipeline configuration is typically handled by the CICS systems programmer, not the application developer. The COBOL developer's responsibility is to write clean copybooks that map naturally to JSON structures and to populate the response fields correctly.

HTTP Status Code Handling

When consuming external web services, robust status code handling is essential. A production program should handle at least these scenarios:

      *--------------------------------------------------------------
      * Evaluate HTTP response status
      *--------------------------------------------------------------
           EVALUATE TRUE
               WHEN WS-HTTP-STATUS = 200
      *            Success — parse the response
                   PERFORM 5000-PARSE-RESPONSE

               WHEN WS-HTTP-STATUS = 401
                   OR WS-HTTP-STATUS = 403
      *            Authentication/authorization failure
                   MOVE 'External service: auth failed'
                       TO WS-LOG-MSG
                   PERFORM 8000-WRITE-LOG
                   MOVE 'Service unavailable (auth)' TO MSGO

               WHEN WS-HTTP-STATUS = 404
      *            Resource not found
                   MOVE 'Record not found at service' TO MSGO

               WHEN WS-HTTP-STATUS = 429
      *            Rate limited — try again later
                   MOVE 'Service busy. Try again.' TO MSGO

               WHEN WS-HTTP-STATUS >= 500
      *            Server error — not our fault
                   MOVE 'External service error' TO MSGO
                   STRING 'HTTP ' WS-HTTP-STATUS
                          ' from creditcheck API'
                       DELIMITED BY SIZE
                       INTO WS-LOG-MSG
                   END-STRING
                   PERFORM 8000-WRITE-LOG

               WHEN OTHER
                   STRING 'Unexpected HTTP status: '
                          WS-HTTP-STATUS
                       DELIMITED BY SIZE
                       INTO WS-LOG-MSG
                   END-STRING
                   PERFORM 8000-WRITE-LOG
                   MOVE 'Service returned unexpected status'
                       TO MSGO
           END-EVALUATE

🔗 Connection Pooling: CICS manages HTTP connections internally. For frequently called services, configure a URIMAP resource that enables connection pooling — CICS reuses TCP connections instead of opening and closing them for each request. This dramatically improves throughput when a CICS transaction makes hundreds of external API calls per second.

30.9 CICS Security: RACF Integration

CICS integrates with RACF (Resource Access Control Facility) — IBM's security product for z/OS — to control who can execute transactions, access files, and invoke programs. Security in CICS operates at multiple levels, and understanding these layers is essential for building applications that protect sensitive data while allowing authorized users to do their work.

Security Layers in CICS

CICS security is enforced through resource-level checking. Each type of resource has its own RACF class:

Resource Type RACF Class Example
Transactions CICSTRN Can user execute transaction AMNT?
Programs CICSPROG Can user invoke program ACCTUPD?
Files CICSFILE Can user read/write to ACCTFILE?
TSQs CICSTQS Can user access the TSQ?
TDQs CICSTDQ Can user write to queue AUDT?
DB2 resources DB2 own classes Managed separately through DB2 authorization

The CICS system programmer defines which resources are protected and at what level. The application programmer's job is to handle authorization failures gracefully rather than letting the user see cryptic CICS error messages.

Checking User Authorization

      *--------------------------------------------------------------
      * Check if the current user is authorized for the operation
      *--------------------------------------------------------------
           EXEC CICS
               QUERY SECURITY
                    RESTYPE('TRANSATTACH')
                    RESID('AMNT')
                    RESIDLENGTH(4)
                    LOGMESSAGE(LOG)
                    RESP(WS-RESP)
           END-EXEC

           EVALUATE WS-RESP
               WHEN DFHRESP(NORMAL)
      *            User is authorized
                   CONTINUE
               WHEN DFHRESP(NOTAUTH)
      *            User is NOT authorized
                   MOVE 'You are not authorized for this function'
                       TO MSGO
                   PERFORM 1500-SEND-DATAONLY
                   EXIT PARAGRAPH
               WHEN OTHER
                   PERFORM 9500-LOG-ERROR
           END-EVALUATE

At GlobalBank, Derek Washington checks authorization before displaying the edit screen for account maintenance. This prevents unauthorized users from even seeing editable fields — a principle known as security at the earliest point. If a user is not authorized, they see the account in read-only mode instead.

Role-Based Screen Behavior

A common pattern is to display different screen layouts based on the user's authorization level:

      *--------------------------------------------------------------
      * Check authorization and set screen mode
      *--------------------------------------------------------------
           EXEC CICS
               QUERY SECURITY
                    RESTYPE('TRANSATTACH')
                    RESID('AMNT')
                    RESIDLENGTH(4)
                    LOGMESSAGE(NOLOG)
                    RESP(WS-RESP)
           END-EXEC

           IF WS-RESP = DFHRESP(NORMAL)
      *        Authorized for maintenance — show editable fields
               MOVE DFHBMPRT TO ANAMEMNTA
               MOVE DFHBMPRT TO ATYPEMNTA
               MOVE 'PF5=Save  PF3=Exit' TO MSGO
           ELSE
      *        Read-only access — protect all fields
               MOVE DFHBMASK TO ANAMEMNTA
               MOVE DFHBMASK TO ATYPEMNTA
               MOVE 'View Only  PF3=Exit' TO MSGO
           END-IF

This approach uses a single program and a single BMS map for both read-only and edit modes. The attribute bytes are set dynamically based on the user's authorization. Users who lack edit permission see the same data but cannot modify it.

Getting User Information

      *--------------------------------------------------------------
      * Get the current user's ID from CICS
      *--------------------------------------------------------------
       01  WS-USER-ID              PIC X(8).

           EXEC CICS
               ASSIGN USERID(WS-USER-ID)
           END-EXEC

           DISPLAY 'Current user: ' WS-USER-ID

30.10 GlobalBank Scenario: Multi-Screen Account Maintenance

Derek Washington is building GlobalBank's account maintenance transaction (AMNT) — a multi-screen application that allows authorized staff to update account information. The transaction has three screens:

  1. Search Screen: Enter account number or search by name
  2. Display/Edit Screen: Show account details with editable fields
  3. Confirmation Screen: Review changes before saving

The State Machine

┌─────────┐  Enter   ┌──────────┐  Enter   ┌─────────────┐
│ SEARCH  │─────────▶│ DISPLAY/ │─────────▶│ CONFIRMATION│
│ Screen  │          │ EDIT     │          │ Screen      │
│ State=S │◀─────────│ State=E  │◀─────────│ State=C     │
└────┬────┘  PF3     └────┬─────┘  PF3     └──────┬──────┘
     │                    │                        │
     │ PF3               │ PF5=Save              │ Enter=Commit
     │                    │ (→ Confirm)            │ PF3=Cancel
     ▼                    │                        │ (→ Edit)
   EXIT                   │                        │
                          ▼                        ▼
                    DB2 UPDATE                  DB2 COMMIT

The COMMAREA Design

       01  WS-COMMAREA.
           05  WS-CA-STATE         PIC X(1).
               88  WS-CA-SEARCH            VALUE 'S'.
               88  WS-CA-EDIT              VALUE 'E'.
               88  WS-CA-CONFIRM           VALUE 'C'.
           05  WS-CA-ACCT-NUM     PIC X(10).
           05  WS-CA-TSQ-NAME     PIC X(8).
      *    Original values for change detection
           05  WS-CA-ORIG-NAME    PIC X(50).
           05  WS-CA-ORIG-TYPE    PIC X(2).
           05  WS-CA-ORIG-STATUS  PIC X(1).
           05  WS-CA-ORIG-BRANCH  PIC X(4).
      *    Modified values pending confirmation
           05  WS-CA-NEW-NAME     PIC X(50).
           05  WS-CA-NEW-TYPE     PIC X(2).
           05  WS-CA-NEW-STATUS   PIC X(1).
           05  WS-CA-NEW-BRANCH   PIC X(4).
       01  WS-COMMAREA-LEN        PIC S9(4) COMP VALUE 133.

The Edit Screen: Detecting Changes

       5000-PROCESS-EDIT-INPUT.
      *--------------------------------------------------------------
      * Receive edited fields and detect changes
      *--------------------------------------------------------------
           EXEC CICS
               RECEIVE MAP('ACCTMNT')
                       MAPSET('MNTSET')
                       INTO(ACCTMNTI)
                       RESP(WS-RESP)
           END-EXEC

           IF WS-RESP = DFHRESP(MAPFAIL)
               MOVE 'No changes detected' TO MSGO
               PERFORM 1500-SEND-EDIT-DATAONLY
               EXIT PARAGRAPH
           END-IF

      *    Check which fields were modified (length > 0)
           MOVE 'N' TO WS-CHANGES-FOUND

           IF ANAMEMNTL > 0
               MOVE ANAMEMNTI TO WS-CA-NEW-NAME
               IF WS-CA-NEW-NAME NOT = WS-CA-ORIG-NAME
                   MOVE 'Y' TO WS-CHANGES-FOUND
               END-IF
           ELSE
               MOVE WS-CA-ORIG-NAME TO WS-CA-NEW-NAME
           END-IF

           IF ATYPEMNTL > 0
               MOVE ATYPEMNTI TO WS-CA-NEW-TYPE
               PERFORM 5100-VALIDATE-ACCT-TYPE
               IF WS-CA-NEW-TYPE NOT = WS-CA-ORIG-TYPE
                   MOVE 'Y' TO WS-CHANGES-FOUND
               END-IF
           ELSE
               MOVE WS-CA-ORIG-TYPE TO WS-CA-NEW-TYPE
           END-IF

           IF WS-CHANGES-FOUND = 'N'
               MOVE 'No changes to save' TO MSGO
               PERFORM 1500-SEND-EDIT-DATAONLY
           ELSE
               PERFORM 6000-SHOW-CONFIRMATION
           END-IF.

The Confirmation Screen: Showing Before/After

       6000-SHOW-CONFIRMATION.
      *--------------------------------------------------------------
      * Display original and new values for review
      *--------------------------------------------------------------
           INITIALIZE ACCTCNFO

           MOVE WS-CA-ACCT-NUM TO CONFACCTO

      *    Show changed fields with before/after
           IF WS-CA-NEW-NAME NOT = WS-CA-ORIG-NAME
               MOVE WS-CA-ORIG-NAME TO CONFNAMBO
               MOVE WS-CA-NEW-NAME  TO CONFNAMAO
               MOVE '*' TO CONFNAMCO
           ELSE
               MOVE WS-CA-ORIG-NAME TO CONFNAMBO
               MOVE '(no change)' TO CONFNAMAO
           END-IF

           IF WS-CA-NEW-TYPE NOT = WS-CA-ORIG-TYPE
               MOVE WS-CA-ORIG-TYPE TO CONFTYPBO
               MOVE WS-CA-NEW-TYPE  TO CONFTYPAO
               MOVE '*' TO CONFTYPC0
           ELSE
               MOVE WS-CA-ORIG-TYPE TO CONFTYPBO
               MOVE '(no change)' TO CONFTYPAO
           END-IF

           MOVE 'Enter=Confirm  PF3=Cancel' TO CONFMSGO
           MOVE 'C' TO WS-CA-STATE

           EXEC CICS
               SEND MAP('ACCTCNF')
                    MAPSET('MNTSET')
                    FROM(ACCTCNFO)
                    ERASE
                    RESP(WS-RESP)
           END-EXEC

           EXEC CICS
               RETURN TRANSID('AMNT')
                      COMMAREA(WS-COMMAREA)
                      LENGTH(WS-COMMAREA-LEN)
           END-EXEC.

The Commit: Optimistic Locking

       7000-COMMIT-CHANGES.
      *--------------------------------------------------------------
      * Apply changes with optimistic locking
      * Check that original values haven't changed since we read them
      *--------------------------------------------------------------
           EXEC SQL
               UPDATE ACCOUNT
               SET ACCT_NAME = :WS-CA-NEW-NAME,
                   ACCT_TYPE = :WS-CA-NEW-TYPE,
                   ACCT_STATUS = :WS-CA-NEW-STATUS,
                   BRANCH_CODE = :WS-CA-NEW-BRANCH,
                   LAST_ACTIVITY = CURRENT TIMESTAMP
               WHERE ACCT_NUMBER = :WS-CA-ACCT-NUM
               AND ACCT_NAME = :WS-CA-ORIG-NAME
               AND ACCT_TYPE = :WS-CA-ORIG-TYPE
               AND ACCT_STATUS = :WS-CA-ORIG-STATUS
               AND BRANCH_CODE = :WS-CA-ORIG-BRANCH
           END-EXEC

           EVALUATE SQLCODE
               WHEN 0
                   IF SQLERRD(3) = 1
                       MOVE 'Changes saved successfully' TO MSGO
                   ELSE
      *                Another user changed the record
                       MOVE 'Record was modified by another '
                         &  'user. Please retry.' TO MSGO
                   END-IF
               WHEN -911
                   MOVE 'Record locked. Please try again.'
                       TO MSGO
               WHEN OTHER
                   STRING 'DB2 error: ' SQLCODE
                       DELIMITED BY SIZE INTO MSGO
                   END-STRING
           END-EVALUATE.

💡 Optimistic Locking: Notice the WHERE clause includes the original values of all changeable fields. This is optimistic locking — we assume no one else will change the record while we are editing it. If someone does, the UPDATE affects 0 rows (SQLERRD(3) = 0) and we detect the conflict. This avoids holding database locks during the user's think time, which could be minutes.

30.11 MedClaim Scenario: Claim History Browse with Scrolling

Sarah Kim needs a CICS screen where customer service representatives can browse a member's complete claim history, scrolling through what could be hundreds of claims.

The Browse Architecture

  1. Representative enters Member ID and presses Enter
  2. Program queries all claims for that member and loads them into a TSQ
  3. Browse screen displays 15 claims per page
  4. PF7/PF8 scroll through the TSQ
  5. Typing 'S' next to a claim and pressing Enter shows the detail
  6. PF3 from detail returns to browse; PF3 from browse exits

Loading the Claims

       2000-LOAD-MEMBER-CLAIMS.
      *--------------------------------------------------------------
      * Load all claims for a member into TSQ
      *--------------------------------------------------------------
      *    Build unique TSQ name
           STRING 'CLH' EIBTRMID
               DELIMITED BY SIZE
               INTO WS-CA-TSQ-NAME
           END-STRING

      *    Clean up any existing queue
           EXEC CICS
               DELETEQ TS QUEUE(WS-CA-TSQ-NAME)
                    RESP(WS-RESP)
           END-EXEC

           EXEC SQL
               DECLARE CLM-HIST-CURSOR CURSOR FOR
                   SELECT C.CLAIM_NUMBER, C.SERVICE_DATE,
                          C.CLAIM_AMOUNT, C.CLAIM_STATUS,
                          P.PROVIDER_NAME, C.SERVICE_CODE
                   FROM CLAIM C
                   JOIN PROVIDER P
                       ON C.PROVIDER_ID = P.PROVIDER_ID
                   WHERE C.MEMBER_ID = :WS-MEMBER-ID
                   ORDER BY C.SERVICE_DATE DESC
                   FETCH FIRST 500 ROWS ONLY
                   WITH UR
           END-EXEC

           EXEC SQL OPEN CLM-HIST-CURSOR END-EXEC

           MOVE 0 TO WS-CA-TOTAL-ITEMS

           PERFORM UNTIL SQLCODE = +100
                      OR WS-CA-TOTAL-ITEMS >= 500
               EXEC SQL
                   FETCH CLM-HIST-CURSOR
                   INTO :WS-BRW-CLM-NUM,
                        :WS-BRW-SVC-DATE,
                        :WS-BRW-AMOUNT,
                        :WS-BRW-STATUS,
                        :WS-BRW-PROVIDER,
                        :WS-BRW-SVC-CODE
               END-EXEC

               IF SQLCODE = 0
                   ADD 1 TO WS-CA-TOTAL-ITEMS

                   EXEC CICS
                       WRITEQ TS QUEUE(WS-CA-TSQ-NAME)
                            FROM(WS-BRW-DATA)
                            LENGTH(WS-BRW-LEN)
                            ITEM(WS-TSQ-ITEM)
                            MAIN
                            RESP(WS-RESP)
                   END-EXEC
               END-IF
           END-PERFORM

           EXEC SQL CLOSE CLM-HIST-CURSOR END-EXEC

           IF WS-CA-TOTAL-ITEMS = 0
               MOVE 'No claims found for this member'
                   TO MSGO
               PERFORM 1500-SEND-SEARCH-DATAONLY
           ELSE
               MOVE 1 TO WS-CA-TOP-ITEM
               MOVE 'B' TO WS-CA-STATE
               PERFORM 3000-DISPLAY-BROWSE-PAGE
           END-IF.

Date Range Filtering and Search Refinement

In production, Sarah Kim finds that many members have hundreds of claims spanning years. Loading all claims into the TSQ is wasteful when the representative only needs recent activity. The search screen includes optional date range fields:

       2000-LOAD-MEMBER-CLAIMS.
      *--------------------------------------------------------------
      * Build dynamic WHERE clause based on search criteria
      *--------------------------------------------------------------
      *    Check for date range filters
           IF WS-FROM-DATE NOT = SPACES
               AND WS-TO-DATE NOT = SPACES
               EXEC SQL
                   DECLARE CLM-HIST-CURSOR CURSOR FOR
                       SELECT C.CLAIM_NUMBER, C.SERVICE_DATE,
                              C.CLAIM_AMOUNT, C.CLAIM_STATUS,
                              P.PROVIDER_NAME, C.SERVICE_CODE
                       FROM CLAIM C
                       JOIN PROVIDER P
                           ON C.PROVIDER_ID = P.PROVIDER_ID
                       WHERE C.MEMBER_ID = :WS-MEMBER-ID
                       AND C.SERVICE_DATE BETWEEN
                           :WS-FROM-DATE AND :WS-TO-DATE
                       ORDER BY C.SERVICE_DATE DESC
                       FETCH FIRST 500 ROWS ONLY
                       WITH UR
               END-EXEC
           ELSE
      *        No date filter — default to last 12 months
               EXEC SQL
                   DECLARE CLM-HIST-ALL CURSOR FOR
                       SELECT C.CLAIM_NUMBER, C.SERVICE_DATE,
                              C.CLAIM_AMOUNT, C.CLAIM_STATUS,
                              P.PROVIDER_NAME, C.SERVICE_CODE
                       FROM CLAIM C
                       JOIN PROVIDER P
                           ON C.PROVIDER_ID = P.PROVIDER_ID
                       WHERE C.MEMBER_ID = :WS-MEMBER-ID
                       AND C.SERVICE_DATE >=
                           CURRENT DATE - 12 MONTHS
                       ORDER BY C.SERVICE_DATE DESC
                       FETCH FIRST 500 ROWS ONLY
                       WITH UR
               END-EXEC
           END-IF

The WITH UR (uncommitted read) isolation level is deliberate here. The browse screen is read-only, so dirty reads are acceptable, and UR avoids taking any locks — essential when browsing a table that is simultaneously being updated by batch adjudication programs.

📊 Performance Note: The date range filter dramatically reduces the number of rows loaded into the TSQ. Without it, a member with 10 years of history might have 2,000+ claims. With a default 12-month window, the typical result set drops to 20-50 claims — a 40x reduction in TSQ storage and DB2 processing time.

Asynchronous Adjudication Trigger

When a claim is submitted through the CICS screen, MedClaim does not adjudicate it immediately. Instead, the submission transaction uses START to trigger an asynchronous adjudication process:

       7000-SUBMIT-CLAIM.
      *--------------------------------------------------------------
      * Insert the claim and trigger async adjudication
      *--------------------------------------------------------------
           EXEC SQL
               INSERT INTO CLAIM
                   (CLAIM_NUMBER, MEMBER_ID, PROVIDER_ID,
                    SERVICE_CODE, SERVICE_DATE, CLAIM_AMOUNT,
                    CLAIM_STATUS, SUBMITTED_BY, SUBMIT_TS)
               VALUES
                   (:WS-CLAIM-NUM, :WS-MEMBER-ID,
                    :WS-PROV-ID, :WS-SVC-CODE,
                    :WS-SVC-DATE, :WS-CLAIM-AMOUNT,
                    'SUBMITTED', :WS-USER-ID,
                    CURRENT TIMESTAMP)
           END-EXEC

           IF SQLCODE = 0
      *        Trigger async adjudication
               MOVE WS-CLAIM-NUM TO WS-ADJ-CLAIM-NUM
               MOVE WS-MEMBER-ID TO WS-ADJ-MEMBER-ID

               EXEC CICS
                   START TRANSID('ADJD')
                         FROM(WS-ADJ-DATA)
                         LENGTH(WS-ADJ-LEN)
                         RESP(WS-RESP)
               END-EXEC

               IF WS-RESP = DFHRESP(NORMAL)
                   STRING 'Claim ' WS-CLAIM-NUM
                          ' submitted. Adjudication pending.'
                       DELIMITED BY '  '
                       INTO MSGO
                   END-STRING
               ELSE
      *            Claim is saved; adjudication will be
      *            picked up by the next batch run
                   STRING 'Claim ' WS-CLAIM-NUM
                          ' submitted. Will be processed'
                          ' in next batch cycle.'
                       DELIMITED BY '  '
                       INTO MSGO
                   END-STRING
               END-IF
           ELSE
               STRING 'Submission failed: SQLCODE='
                      SQLCODE
                   DELIMITED BY SIZE
                   INTO MSGO
               END-STRING
           END-IF.

This pattern — immediate INSERT with asynchronous downstream processing — is a hallmark of well-designed CICS applications. The user gets an immediate confirmation that their data was saved, while the expensive adjudication logic runs in a separate task without tying up the user's terminal.

The Claim Submission Workflow

MedClaim also needs a multi-screen claim submission workflow:

Screen 1: Enter member ID → validate member exists
Screen 2: Enter claim details (provider, service, amount)
Screen 3: Review and confirm → INSERT into CLAIM table

The key challenge is validation at each step:

       5000-VALIDATE-CLAIM-ENTRY.
      *--------------------------------------------------------------
      * Validate claim data before confirmation
      *--------------------------------------------------------------
           MOVE 'Y' TO WS-VALID-FLAG

      *    Validate provider exists and is in network
           EXEC SQL
               SELECT PROVIDER_NAME, NETWORK_STATUS
               INTO :WS-PROV-NAME, :WS-NET-STATUS
               FROM PROVIDER
               WHERE PROVIDER_ID = :WS-PROV-ID
               WITH UR
           END-EXEC

           IF SQLCODE = +100
               MOVE 'Provider ID not found' TO MSGO
               MOVE 'N' TO WS-VALID-FLAG
               EXIT PARAGRAPH
           END-IF

      *    Validate service code
           EXEC CICS
               LINK PROGRAM('SVCVALID')
                    COMMAREA(WS-SVC-VALIDATION)
                    LENGTH(WS-SVC-LEN)
                    RESP(WS-RESP)
           END-EXEC

           IF WS-SVC-RETURN-CODE NOT = 0
               MOVE WS-SVC-RETURN-MSG TO MSGO
               MOVE 'N' TO WS-VALID-FLAG
               EXIT PARAGRAPH
           END-IF

      *    Validate amount within limits
           IF WS-CLAIM-AMOUNT > 99999.99
               MOVE 'Amount exceeds maximum' TO MSGO
               MOVE 'N' TO WS-VALID-FLAG
           END-IF.

30.12 Defensive Programming in CICS

CICS error handling requires special vigilance because a misbehaving program affects all users sharing the CICS region.

The CICS Defensive Programming Checklist

Practice Why It Matters
Check RESP after every EXEC CICS command Unchecked errors lead to unpredictable behavior
Handle MAPFAIL on every RECEIVE MAP Users press Enter without typing — it is normal
Initialize output maps before populating Prevents ghost data from previous interactions
Clean up TSQs on exit (PF3) and on first entry Prevents storage leaks and stale data
Never use STOP RUN, COBOL file I/O, or ACCEPT These crash or hang the CICS region
Keep transactions short (< 1 second) Long tasks starve other users
Use unique TSQ names (include EIBTRMID) Prevents cross-user data contamination
Implement HANDLE ABEND for recovery Graceful degradation instead of cryptic errors
Use optimistic locking for updates Avoids holding DB2 locks during think time
Log errors to TDQ before abending Provides diagnostic information for support
Validate all user input before processing Prevents garbage data and SQL errors
Test with multiple concurrent users Single-user testing misses concurrency bugs

Common Production Issues

Storage leaks from orphaned TSQs. If a user closes their terminal emulator instead of pressing PF3, the TSQ is never deleted. Implement a cleanup transaction that runs periodically (via START with INTERVAL) to delete TSQs older than a threshold.

      *--------------------------------------------------------------
      * TSQ cleanup transaction (runs every 30 minutes)
      *--------------------------------------------------------------
       PROCEDURE DIVISION.
      *    Browse all TSQs matching our prefix
           EXEC CICS
               STARTBROWSE TEMPQUEUE
                    RESP(WS-RESP)
           END-EXEC

           PERFORM UNTIL WS-RESP NOT = DFHRESP(NORMAL)
               EXEC CICS
                   GETNEXT TEMPQUEUE(WS-TSQ-NAME)
                        RESP(WS-RESP)
               END-EXEC

               IF WS-RESP = DFHRESP(NORMAL)
                   IF WS-TSQ-NAME(1:3) = 'BRW'
                       OR WS-TSQ-NAME(1:3) = 'CLH'
      *                Check age and delete if stale
                       PERFORM 2000-CHECK-AND-DELETE
                   END-IF
               END-IF
           END-PERFORM

           EXEC CICS
               ENDBROWSE TEMPQUEUE
           END-EXEC

      *    Schedule next cleanup
           EXEC CICS
               START TRANSID('CLNP')
                     INTERVAL(003000)
           END-EXEC

           EXEC CICS RETURN END-EXEC.

Cross-terminal data contamination. If two users run the same transaction with a hard-coded TSQ name like 'BRWQUEUE', their browse data will be interleaved in the same queue. Always include the terminal ID (EIBTRMID) in the TSQ name. At GlobalBank, the naming convention is PREFIX + EIBTRMID — for example, 'BRWT104' for a browse TSQ on terminal T104. Maria Chen enforces this in code reviews.

COMMAREA overwrite on first invocation. When a pseudo-conversational program is first invoked (EIBCALEN = 0), the DFHCOMMAREA pointer may be invalid. Referencing it causes an ASRA abend. The solution is the standard entry pattern:

       0000-MAIN.
           EVALUATE TRUE
               WHEN EIBCALEN = 0
      *            First invocation — initialize
                   INITIALIZE WS-COMMAREA
                   PERFORM 1000-FIRST-TIME
               WHEN OTHER
      *            Subsequent invocation — restore state
                   MOVE DFHCOMMAREA TO WS-COMMAREA
                   PERFORM 2000-PROCESS-INPUT
           END-EVALUATE.

Transaction timeout. If a CICS task runs too long, CICS abends it with AKCT (task purge timeout). This typically indicates an infinite loop, a DB2 deadlock that was not handled, or a call to an external service that did not respond.

ASRA abends (program check). These are the CICS equivalent of a COBOL runtime error — division by zero, subscript out of range, invalid address. They are almost always caused by: 1. Referencing a field in the COMMAREA when EIBCALEN = 0 2. Array subscript exceeding the OCCURS count 3. Numeric field containing non-numeric data

🔴 The ASRA Trap: The most common cause of ASRA in pseudo-conversational programs is accessing DFHCOMMAREA when EIBCALEN is 0 (first invocation). Always check EIBCALEN before referencing DFHCOMMAREA. This is why the first line of your main logic should be EVALUATE TRUE WHEN EIBCALEN = 0....

30.13 Putting It All Together: Transaction Design Patterns

Let us consolidate the patterns from this chapter and Chapter 29 into a reference of standard CICS transaction designs:

Pattern 1: Simple Inquiry (Chapter 29)

COMMAREA: State + Key
Screens: 1 (inquiry/display combined)
Flow: First → Send empty map → RETURN → Receive input → Lookup → Display → RETURN

Pattern 2: Browse with Detail (This Chapter)

COMMAREA: State + TSQ name + Position + Total + Search key
Storage: TSQ for browse data
Screens: 2 (search/browse combined, detail)
Flow: Search → Load TSQ → Browse → PF7/8 scroll → Select → Detail → PF3 → Browse

Pattern 3: Multi-Screen Maintenance (This Chapter)

COMMAREA: State + Key + Original values + New values
Screens: 3 (search, edit, confirm)
Flow: Search → Display → Edit → Validate → Confirm → UPDATE → Success message

Pattern 4: Wizard/Workflow (Advanced)

COMMAREA: State + Step number + Accumulated data
Storage: TSQ for large intermediate data
Screens: N (one per step)
Flow: Step 1 → Validate → Step 2 → ... → Step N → Confirm → Commit
PF3 at any step goes back one step (not exit)

Summary

This chapter has advanced your CICS skills from simple inquiry screens to production-quality multi-screen applications. You have learned to use Temporary Storage Queues for browse/scroll patterns and multi-screen state, Transient Data Queues for logging and event-driven processing, the START command for asynchronous operations, channels and containers for modern program communication, and HANDLE ABEND for error recovery.

The browse/scroll pattern — loading data into a TSQ, displaying pages, and handling PF7/PF8 — is one of the most common patterns in CICS programming. The multi-screen maintenance pattern — search, edit, confirm, commit with optimistic locking — is the other. Together, they account for the majority of CICS transactions in production today.

The defensive programming theme is particularly critical in CICS. Unlike batch programs where a bug affects one job, a bug in a CICS program can affect every user in the region. Check every RESP, handle every MAPFAIL, clean up every TSQ, and keep every transaction short.

In the next chapter, we move from the DB2/CICS world to explore IMS DB — an alternative database technology that predates DB2 and remains in wide use across many mainframe installations.


"I tell my team: if you write a CICS program that works perfectly for one user, you are halfway there. The other half is making it work perfectly when 2,000 users hit it simultaneously, when the DB2 subsystem is under load, and when someone turns off their terminal in the middle of a transaction. That is the real test." — James Okafor, team lead, MedClaim