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
1 LINKAGE SECTION.2 01 DFHCOMMAREA PIC X(120).3 PROCEDURE DIVISION.4 IF EIBCALEN = 05 PERFORM 1000-SEND-SEARCH-MAP6 ELSE7 MOVE DFHCOMMAREA TO WS-CA8 EVALUATE TRUE9 WHEN EIBAID = DFHPF310 PERFORM 9000-EXIT-TO-MENU11 WHEN CA-STEP-SEARCH12 PERFORM 2000-SHOW-ACCOUNT13 WHEN CA-STEP-CHANGE14 PERFORM 3000-APPLY-CHANGE15 END-EVALUATE16 END-IF17 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-EXEC24 EXEC CICS READ FILE('ACCTMST') INTO(ACCT-REC)25 RIDFLD(CA-ACCT-NO) UPDATE END-EXEC26 IF ACCT-REC NOT = CA-BEFORE-IMAGE27 MOVE 'RECORD CHANGED BY ANOTHER USER'28 TO MSGO29 PERFORM 2100-RESEND-DETAIL30 ELSE31 PERFORM 3100-EDIT-AND-REWRITE32 END-IF.
Modern: Stateless REST resource with explicit versioning
1@GetMapping("/accounts/{no}")2ResponseEntity<Account> get(@PathVariable String no) {3 Account a = accounts.find(no); // 2000-SHOW4 return ResponseEntity.ok()5 .eTag(a.version())6 .body(a);7}89@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-Match15 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
1paths:2 /accounts/{accountNo}:3 patch:4 operationId: changeAccount5 summary: ACIQ step CHANGE (ACCTINQ 3000-APPLY-CHANGE)6 parameters:7 - $ref: '#/components/parameters/AccountNo'8 - name: If-Match9 in: header10 required: true11 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 read21 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.
Related patterns
Usually found in the same codebase.
- Data storesHigh
VSAM alternate index coupling
Maps to Relational table with secondary index and explicit queries
- .NETHigh
WebForms ViewState and code-behind logic
Maps to SPA front end over a stateless API that rereads authoritative data
- COBOLCritical
ALTER statement
Maps to Explicit state machine
- SOAPMedium
SOAP and WSDL schema coupling
Maps to OpenAPI 3.1 with JSON Schema 2020-12 and problem details
- Next in catalog
JCL condition code chains
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.