Skip to content
PYD ENTERPRISES

CICS anti-pattern

CICS pseudo-conversational state

Each screen interaction is a separate CICS task, and the conversation's state survives only in the COMMAREA passed on RETURN TRANSID, so business flow is spread across task boundaries.

FAMILY
CICS
CUTOVER RISK
Critical
Changes runtime behavior in ways a line-by-line reading misses. Resolve before any cutover plan.
MODERN MAPPING
Stateless REST resource with explicit versioning
GENERATED ARTIFACT
openapi.yaml

Before and after

legacy source → modern mapping

Legacy: exhibits the pattern

ACCTINQ.cblCOBOL
1 LINKAGE SECTION.
2 01 DFHCOMMAREA PIC X(120).
3 PROCEDURE DIVISION.
4 IF EIBCALEN = 0
5 PERFORM 1000-SEND-SEARCH-MAP
6 ELSE
7 MOVE DFHCOMMAREA TO WS-CA
8 EVALUATE TRUE
9 WHEN EIBAID = DFHPF3
10 PERFORM 9000-EXIT-TO-MENU
11 WHEN CA-STEP-SEARCH
12 PERFORM 2000-SHOW-ACCOUNT
13 WHEN CA-STEP-CHANGE
14 PERFORM 3000-APPLY-CHANGE
15 END-EVALUATE
16 END-IF
17 EXEC CICS RETURN TRANSID('ACIQ')
18 COMMAREA(WS-CA)
19 LENGTH(LENGTH OF WS-CA)
20 END-EXEC.
21 3000-APPLY-CHANGE.
22 EXEC CICS RECEIVE MAP('ACCTM2') MAPSET('ACCTMS')
23 INTO(ACCTM2I) END-EXEC
24 EXEC CICS READ FILE('ACCTMST') INTO(ACCT-REC)
25 RIDFLD(CA-ACCT-NO) UPDATE END-EXEC
26 IF ACCT-REC NOT = CA-BEFORE-IMAGE
27 MOVE 'RECORD CHANGED BY ANOTHER USER'
28 TO MSGO
29 PERFORM 2100-RESEND-DETAIL
30 ELSE
31 PERFORM 3100-EDIT-AND-REWRITE
32 END-IF.

Modern: Stateless REST resource with explicit versioning

AccountController.javaJava
1@GetMapping("/accounts/{no}")
2ResponseEntity<Account> get(@PathVariable String no) {
3 Account a = accounts.find(no); // 2000-SHOW
4 return ResponseEntity.ok()
5 .eTag(a.version())
6 .body(a);
7}
8
9@PatchMapping("/accounts/{no}")
10ResponseEntity<Account> change(
11 @PathVariable String no,
12 @RequestHeader("If-Match") String etag,
13 @RequestBody AccountChange body) {
14 // CA-BEFORE-IMAGE compare -> If-Match
15 Account updated = accounts.apply(no, etag, body);
16 return ResponseEntity.ok()
17 .eTag(updated.version())
18 .body(updated);
19}

Why it is dangerous in modernization

Pseudo-conversational CICS programs end their task after sending a map and ask CICS to start the same transaction when the user presses a key: EXEC CICS RETURN TRANSID('ACIQ') COMMAREA(WS-CA). On the next task, EIBCALEN tells the program whether this is a first entry, and a step field inside the COMMAREA tells it which screen it is on. The business flow (search, select, change, confirm) is a state machine encoded in that step field and in EIBAID, the attention key the user pressed. No database lock is held between screens, so programs keep a before image of the record in the COMMAREA and compare it on update. A migration that maps each program to one REST endpoint loses the state machine, the PF key branches and the optimistic concurrency check, and the new API happily overwrites changes made by another user between screens.

How PYD ENTERPRISES detects it

The CICS front end parses EXEC CICS commands as typed AST nodes, not comments. It finds the EIBCALEN test that separates first entry from continuation, resolves the COMMAREA layout from DFHCOMMAREA in the linkage section and the RETURN TRANSID COMMAREA operand, and identifies the step field by tracking which COMMAREA fields select branches on entry. Branches on EIBAID against DFHAID constants become key events. The result is a cross-task state machine: states from the step field, transitions labelled by key and validation outcome, and the before-image comparison recognised as an optimistic concurrency check. BMS map fields from RECEIVE MAP become request properties.

Maps to: Stateless REST resource with explicit versioning

Each state transition that changes data becomes an API operation, and the conversation state moves to the client or to an explicit workflow. The before-image comparison maps to an ETag or version field checked on update, returning 409 or 412 on conflict. Screen-only states (paging, redisplay after a validation error) usually disappear from the API and live in the new front end.

Generated specification fragment

openapi.yamlYAML
1paths:
2 /accounts/{accountNo}:
3 patch:
4 operationId: changeAccount
5 summary: ACIQ step CHANGE (ACCTINQ 3000-APPLY-CHANGE)
6 parameters:
7 - $ref: '#/components/parameters/AccountNo'
8 - name: If-Match
9 in: header
10 required: true
11 schema: { type: string }
12 requestBody:
13 content:
14 application/json:
15 schema:
16 $ref: '#/components/schemas/AccountChange'
17 responses:
18 '200': { description: Account updated }
19 '412':
20 description: Changed by another user since read
21 x-pyd-source: ACCTINQ.cbl:21-32

Every generated element links back to the source file and line range shown in the legacy pane. Review and sign off before building against it.

Reading the conversation, not the program

A single pseudo-conversational program usually implements several screens. ACCTINQ above sends a search map on first entry, shows an account when the step is SEARCH, and applies a change when the step is CHANGE. PF3 exits to a menu from any state. Read top to bottom, this is one procedure with a few branches. Run, it is a four-state machine whose transitions happen in different CICS tasks, minutes apart, with only 120 bytes of COMMAREA carrying the state between them.

PYD ENTERPRISES draws that machine explicitly and exports it as a Mermaid state diagram next to the OpenAPI document, so the API reviewer and the UX team are looking at the same flow.

Concurrency without locks

Because the task ends after each screen, the program cannot hold the VSAM record for update while the user types. Instead it saves the record in CA-BEFORE-IMAGE when it displays it, and on the change step it rereads with UPDATE and compares. That comparison is a business-critical rule disguised as a MOVE and an IF. In the new API it is the If-Match header and a 412 response. Dropping it is one of the most common defects in CICS-to-REST migrations, and it does not show up in testing until two users edit the same account.

Variants the engine handles

  • Programs that keep state in a temporary storage queue named from EIBTRMID instead of the COMMAREA.
  • Channels and containers (PUT CONTAINER, GET CONTAINER) in newer code, which can carry more than the 32 KB COMMAREA limit.
  • XCTL chains where one program passes the COMMAREA to another, so a single conversation spans programs.
  • RETURN with a different TRANSID, which hands the next screen to another transaction.

In each case the state store is resolved and the machine is drawn across every program that touches it.

Find every CICS pseudo-conversational state in your codebase.

Run PYD ENTERPRISES in your private VPC and get each occurrence with file, line range, modern mapping and equivalence fixtures, ready for your architects to review.