Skip to content
Aug 14, 2026ยท8 min read

A CICS transaction is not an HTTP request

A CICS transaction is not an HTTP request. Model task state, COMMAREA data, syncpoints and distributed commit without breaking business behavior.

A CICS transaction is not an HTTP request

The dangerous CICS modernization is the one that looks obvious. A team finds a transaction code, wraps its input in JSON, gives it a POST route, and declares the boundary understood. The screens work in a demo. Then duplicate submissions, stale state, early commits, and half-finished distributed updates appear under real traffic.

A CICS transaction is a task with CICS-managed resources, recovery rules, and a precise end. An HTTP request is a transport exchange whose server may lose the client before either side knows what happened. They can carry the same business command, but they do not define the same unit of work. A safe rewrite preserves the business state machine and makes the commit boundary explicit instead of letting the web framework invent one.

The task boundary owns the semantics

A CICS transaction begins when CICS attaches a task and ends when the top-level program returns control or the task terminates. That task can invoke several programs with LINK or transfer control with XCTL. Those program calls do not create new transactions merely because control crosses a module boundary. The transaction ID selects an entry point; it does not describe a request method, a resource, or a response contract.

HTTP supplies a different envelope. A request has headers, a body, a connection, and a response, but the protocol does not know which database writes form one business action. A server can commit before it sends the response. The client can time out after the commit and retry. A proxy can retry a request the application never classified as safe. None of those events has a direct equivalent in the CICS task model.

The first design question is therefore not which URL should replace a four-character transaction ID. Ask which recoverable changes must succeed or fail together, what input starts that decision, and what durable fact proves it completed. Only then can a route represent the command honestly.

This distinction also prevents a common granularity error. One screen interaction may run one CICS task, several linked programs, and updates across Db2 and recoverable CICS resources. Splitting each program into a network service inserts failure points inside a unit that used to fail as one. Combining several pseudo-conversational steps into one long HTTP request makes the opposite error: it holds work open while a person thinks.

Treat the legacy call graph as evidence, not as the target architecture. Mark task starts, LINK and XCTL edges, file and database updates, outbound messages, explicit syncpoints, and top-level returns. The boxes you draw around recoverable work will rarely match either the screen map or the program boundaries.

Pseudo-conversation releases the task between screens

A pseudo-conversational application looks like a session to the user but runs as a series of short tasks. A task receives input, reconstructs enough context to process it, sends the next screen, names the transaction that should handle the next input, passes continuation data, and returns to CICS. While the user reads the screen, no application task waits for the reply.

IBM's CICS documentation describes this as nonconversational transactions embedded in a sequence. The design saves storage and avoids holding exclusive resources during human think time. That wording matters because the apparent conversation is a user experience, not one continuous transaction.

A simplified COBOL exit often has this shape:

       EXEC CICS SEND MAP('ACCT1') MAPSET('ACCT')
       END-EXEC
       EXEC CICS RETURN
            TRANSID('AC02')
            COMMAREA(WS-CONTINUATION)
            LENGTH(WS-CONTINUATION-LEN)
       END-EXEC

RETURN TRANSID does not call AC02 immediately in the ordinary case. It tells CICS which transaction should receive the next terminal input. The current task ends. When that input arrives, CICS attaches a new task, and the first program can address the passed COMMAREA. A channel can play the continuation role too.

Mapping this sequence to a single HTTP session object usually hides two independent things. The browser has presentation state such as the current page and editable fields. The application has workflow state such as the account selected, the version read, the permissions checked, and the next commands allowed. The second category needs a versioned server-side representation or a tamper-resistant token. A process-local session map is neither durable enough nor clear enough for that job.

The modern boundary should expose each user decision as a short command. A GET can fetch a projection for display. A POST can submit a decision against a workflow version. No database transaction remains open while the browser waits. That is close to the operational intent of pseudo-conversation even though the transport looks entirely different.

A COMMAREA is a continuation contract, not a session

A COMMAREA is a byte contract passed between programs or successive tasks. Its copybook gives those bytes meaning. It may contain a function code, identifiers, flags, display fields, return codes, and data the next task needs. It can also contain historical debris that no live path reads. Calling the whole structure session state avoids the analysis the migration requires.

For a pseudo-conversation, CICS keeps the passed COMMAREA available for the first program in the next task. IBM documents an important limit: the COMMAREA is not recoverable. If a task commits a database change and then prepares continuation bytes, those bytes do not become part of the same recoverable resource merely because CICS carries them forward.

The practical size limit is another warning against treating it as an object store. The documented theoretical ceiling on RETURN is around 32 KB, with IBM guidance historically recommending a lower safe size. Channels and containers remove the single COMMAREA size constraint and give data named compartments, but they do not turn continuation data into durable business truth.

Decode each COMMAREA into three categories. Business identity includes stable values such as customer number, claim ID, or order ID. Workflow control includes stage, function code, prior action, and optimistic version. Presentation residue includes copied labels, screen literals, cursor choices, and fields that can be fetched again. Store business state durably, model workflow control explicitly, and discard presentation residue unless observable behavior depends on it.

Length handling belongs in the contract. A receiving COBOL program often checks EIBCALEN before reading DFHCOMMAREA; later versions of a copybook may append fields. A JSON decoder that insists every new field exists can be less compatible than the old program. Record the accepted lengths, initialization rules, space and zero conventions, EBCDIC conversion, packed-decimal formats, and redefinition branches before designing a typed replacement.

Do not serialize the copybook as a giant JSON document and call that preservation. That exposes storage layout as a public API, carries fields that clients should never control, and makes every copybook change an API change. Translate the bytes into a command with named intent, then retain the raw input only in test fixtures and audit evidence where policy permits.

A syncpoint commits a unit of work, not a response

CICS commits recoverable changes at a syncpoint. The application can issue EXEC CICS SYNCPOINT, and CICS also takes an implicit syncpoint when a top-level task ends normally. An abend normally triggers dynamic transaction backout for changes made to recoverable resources in the current unit of work. That lifecycle has no automatic relationship to whether an HTTP response reached its caller.

The consequence is easy to miss in a wrapper. Suppose the task updates Db2, writes a recoverable queue, returns normally, and the gateway loses its connection before delivering the reply. CICS has committed. The caller sees a timeout. If the replacement interprets timeout as failure and repeats the command without an idempotency rule, it performs the business action twice.

The reverse failure is possible too. A web handler may write a 200 OK status into a buffer, then fail when its database commit runs after application code returns. Framework abstractions can make response construction look like completion even when durable completion has not happened. The replacement must define success as a committed business outcome and arrange response emission around that fact.

Inventory every explicit syncpoint rather than assuming end-of-task is the only one. An explicit syncpoint closes the current unit of work and starts another while the task continues. Rollback can reverse only changes since the last syncpoint, not everything the task has ever done. If a program commits an audit row halfway through and later backs out an account update, a rewrite that wraps the entire handler in one database transaction changes visible recovery behavior.

Nonrecoverable effects need separate treatment. A call to an external service, an email handed to a nontransactional system, or a write to a nonrecoverable destination will not roll back because the Db2 work does. The old application may rely on ordering, retries, or operator repair around those effects. Preserve the outcome, not the comforting fiction that one language-level transaction controls every resource.

Two-phase commit cannot be replaced by hopeful retries

Trace every syncpoint first
Whole-codebase analysis follows updates, returns, and commit ownership before the target design is generated.

Two-phase commit coordinates recoverable resource managers in one distributed unit of work. In phase one, participants prepare and promise they can honor the decision. In phase two, the coordinator tells them to commit or back out. If communication fails after prepare, a participant can remain in doubt until it learns the coordinator's decision. That uncertainty is a protocol state, not an ordinary application error.

CICS can coordinate distributed work across eligible resources and conversations. IBM's syncpoint documentation says that a distributed process should have one syncpoint initiator, while agents can accept the request or force rollback. The recovery manager logs enough state to resynchronize work after a connection returns. A chain of HTTP calls has none of this behavior by default.

Replacing a distributed unit of work with service A calls service B, then retries on error creates an unowned gap. If B commits and A loses the response, A cannot infer whether to retry, compensate, or report success. A retry can duplicate the effect. A compensation can reverse an action that never happened. Returning an error can tell the user the action failed when it completed.

There are two honest replacement patterns. Keep atomic work inside one transactional boundary when the data can live under one resource manager. If services must own separate data, use an explicit workflow with durable commands, idempotent consumers, an outbox or equivalent atomic message record, and compensations designed as business operations. The second pattern gives up instantaneous atomicity; it must expose pending, completed, rejected, and repair states.

Do not label the second pattern two-phase commit. A saga coordinates separate commits and possible compensations. Two-phase commit coordinates one decision across prepared participants. Blurring them leads teams to promise atomic behavior while implementing eventual repair.

The familiar wrapper failure has a precise sequence

The most useful failure review follows one command across the boundary and records what each side can know. Consider a CICS payment task exposed through a POST endpoint. The task checks the account, updates Db2, writes a recoverable record for downstream processing, and returns normally.

  1. The client sends a request with payment reference P7319.
  2. The gateway starts the CICS task and waits.
  3. The task updates both recoverable resources and reaches its end-of-task syncpoint.
  4. CICS commits, but the gateway connection closes before the response reaches the client.
  5. The client retries because it observed a timeout, and a second task receives the same business command.

If the program generates a new reference on each invocation, the second task cannot recognize the first. If it checks only current balance, both attempts may still pass. If the HTTP layer generates an idempotency token but does not place it inside the same commit as the business update, a crash can leave the token and payment disagreeing.

The fix starts with a client-stable command ID and one atomic claim on that ID. Within the unit that commits the payment, store the command ID, a request fingerprint, status, and result reference. A duplicate with the same fingerprint returns the recorded outcome. A duplicate ID with different content is a conflict. A request whose outcome is still pending gets a truthful pending response rather than a blind retry.

A minimal boundary response might look like this:

{
  "command_id": "P7319",
  "workflow_version": 12,
  "status": "completed",
  "result_ref": "PAY-88421"
}

This record does more than suppress duplicates. It gives operations a durable answer when the transport transcript is ambiguous. It also lets a replacement match the old system's committed outcome without pretending that TCP delivery and commit were one event.

Model the boundary as commands and states

The replacement boundary should make business intent, workflow state, and commit ownership visible. Start with a state transition table rather than controllers. For each command, name the allowed prior states, required version, validation, recoverable writes, nonrecoverable effects, resulting state, and duplicate behavior.

A useful command envelope is deliberately smaller than the COMMAREA:

{
  "command_id": "7f6c2b1a",
  "workflow_id": "CLAIM-2048",
  "expected_version": 4,
  "action": "approve",
  "input": {
    "amount": "125.00",
    "currency": "USD"
  }
}

The handler loads CLAIM-2048, verifies version 4 and the approve transition, applies the business rules, writes the new state plus the command result in one local transaction, and then returns the recorded result. If downstream work cannot join that transaction, the same commit writes an outbox record. A dispatcher can retry delivery without repeating the business transition.

Keep protocol concepts out of the domain model. HTTP status codes describe the request exchange; they should map from domain outcomes. A stale workflow version may map to 409 Conflict. A command already completed with the same fingerprint can return its original result. A new command accepted for asynchronous processing can return 202 Accepted with a status reference. Those mappings are boundary policy, not account or claim rules.

The pseudo-conversational next transaction ID becomes an allowed-transition decision, not a redirect disguised as business logic. The replacement UI asks the workflow representation which actions are available. The server still enforces them. A caller cannot skip from review to completion merely by guessing a route.

This design also removes terminal affinity without losing sequence. Any service instance can handle the next command because durable state carries the workflow version and outcome. If some state must remain private to the client, sign and version it, validate its age, and assume the user can replay it. Do not put authority, prices, or permission decisions in an unsigned browser token.

Idempotency needs a recovery contract

Retire terminal-shaped architecture
The rewrite preserves behavior while moving CICS workflows into explicit services, clients, and Postgres state.

An idempotency header alone does not make a command safe. The system needs rules for scope, persistence, content matching, concurrent arrival, retention, and recovery. Otherwise the header is decoration around the same ambiguous failure.

Scope the key to the actor and operation so unrelated clients cannot collide. Bind it to a canonical request fingerprint. Enforce uniqueness in the database transaction that owns the business change. Return the stored result only when the fingerprint matches. Decide how long records remain authoritative based on the business replay window, not a convenient cache expiry.

Concurrent duplicates need one winner. A unique constraint or locked command row can claim execution. Other attempts should observe pending and poll, or wait within a strict bound, rather than start the work again. If the winner crashes before commit, its claim and business writes should roll back together. If it crashes after commit, later attempts should find the completed record.

Recovery also needs operator-visible states. received, completed, and rejected cover the clean path, while dispatch_pending, compensation_pending, and manual_review describe work that crossed a nontransactional boundary. Do not flatten all of them into HTTP 500. An operator needs the command ID, workflow ID, last durable transition, attempted effect, and safe next action.

Retries belong at the layer that knows whether the operation is repeatable. A transport library may retry connection establishment or a read-only fetch. It should not silently retry a state-changing POST unless the application supplies the idempotency contract. Inside the system, an outbox dispatcher can retry a message because the consumer deduplicates by message ID and the producer has already committed the intent.

This is stricter than many CICS wrappers, but it expresses protection CICS previously supplied through task recovery and coordinated syncpointing. Once the work crosses plain HTTP and independent databases, the application must own the uncertainty explicitly.

The adapter must declare who owns commit

An HTTP adapter can run outside CICS, inside a CICS region, or across a gateway and distributed program link. The placement changes latency and operations, but commit ownership matters more. The component that returns success must know whether the unit of work committed, and it must never imply that a remote call joins its local transaction unless a supported coordinator actually makes that true.

A thin adapter outside CICS should treat the legacy transaction as a command processor with an ambiguous transport outcome. It sends a stable command ID, waits for a result, and can query that result after a timeout. It does not begin a local database transaction, call CICS, update its own table, and assume both commits form one action. Without distributed coordination, that sequence has two commits and a failure gap between them.

An adapter using distributed program link needs equal care. A linked server program does not always own syncpoint. IBM documents that a DPL server without SYNCONRETURN cannot issue an independent syncpoint; the client owns the distributed unit of work. With SYNCONRETURN, the server can commit independently when it returns, but that also separates its changes from the caller's earlier work. This option is a transaction design choice, not a performance flag.

Before choosing placement, write down the answers to four questions:

  • Which process assigns the stable command ID?
  • Which resource stores the authoritative command outcome?
  • Which coordinator, if any, covers every recoverable participant?
  • How does a caller resolve a timeout without repeating the effect?

If the answers name two independent databases and no coordinator, design an asynchronous boundary. Commit the command and outbox record together on the initiating side. Let the CICS side claim the command ID and record its outcome with the legacy update. Reconcile acknowledgements without treating their delivery as the business commit. This adds states, but those states describe uncertainty that already exists.

Synchronous HTTP can still front that workflow. The adapter may wait briefly for completed or rejected, then return 202 Accepted if processing continues. A status read reports the durable command outcome. The user sees a quick completion in the normal case and a truthful pending state during delays. Do not keep the HTTP connection open indefinitely to mimic a conversational task.

Security boundaries should follow the same explicit model. Authenticate the caller at the web edge, but pass a constrained principal and authorized command context to the transaction handler. Do not trust an account number, operator code, or authorization flag simply because it once lived in a protected COMMAREA. Values that cross a browser or message broker must be validated again at the authority that applies the transition.

Keep the original CICS response code and diagnostic context inside the adapter's evidence record, then map them to a small public error contract. Exposing every EIBRESP value couples callers to the implementation. Discarding them makes parity debugging and operator repair harder. The boundary needs both views: a stable domain result for callers and enough legacy detail for the team proving equivalent behavior.

Parity testing must include broken conversations

Handle the million-line estate
The platform reads systems over a million lines and analyzes every source language in parallel.

Happy-path field comparison will not prove this migration. The parity harness must compare committed behavior across task boundaries, retries, abends, stale continuation data, and failures near syncpoint. A screen that displays the same totals can still conceal a different unit of work.

Build traces from recorded production traffic only after applying the organization's data handling rules. For each case, capture initial durable state, input bytes and decoded fields, invoked transaction, resource changes, syncpoint locations, output fields, next transaction, and final durable state. Masking must preserve distinctions that drive branches, such as spaces versus low values or two codes that share a display label.

Run the old and new paths against isolated, resettable data. Compare business outcomes, not implementation noise. Timestamps, generated identifiers, and record order may need normalization, while amounts, statuses, authorization decisions, and committed side effects must match exactly.

The failure matrix should include at least these checks:

  • Repeat the same command before and after completion.
  • Drop the response after commit, then retry with the same command ID.
  • Force failure before syncpoint and immediately after a nonrecoverable effect.
  • Submit a valid command with a stale workflow version.
  • Resume with every supported COMMAREA length and version.

CodeHero uses a parity harness against recorded production traffic when rewriting CICS and adjacent legacy code, because source-level similarity cannot reveal these recovery differences. The same harness should inject boundary failures, not merely replay clean requests, before anyone trusts the replacement with writes.

Preserve behavior without preserving the accident

Correct modeling does not require recreating CICS inside a web service. It requires preserving the rules users and connected systems can observe: which commands are allowed, which changes commit together, what a duplicate does, how unfinished work appears, and how recovery reaches a known state.

Some legacy details deserve retirement. A four-character transaction ID need not become a route name. A COMMAREA layout need not become a public payload. Terminal affinity need not become sticky sessions. A resource manager boundary should survive until the team has deliberately replaced its atomicity with a workflow and repair model.

Write one boundary specification for each business command. Include its stable identity, accepted prior state, version rule, transaction owner, effects inside the commit, effects outside it, duplicate result, and operator recovery action. Review that document with the people who diagnose CICS failures today. They will usually find an implicit syncpoint, queue behavior, or restart assumption missing from the first draft.

The hard test is a lost response after a successful commit. If the new design can state exactly what the client retries, what the server reads, what result it returns, and why no business effect repeats, the boundary is probably real. If the answer depends on the request and transaction ending together, the design still mistakes HTTP for CICS.

FAQ

Is a CICS transaction the same as a database transaction?

No. A CICS transaction is an executing task selected by a transaction ID, while a database transaction is one participant in its unit of work. CICS can coordinate Db2 and other recoverable resources at a syncpoint.

Can one CICS transaction become one REST endpoint?

Sometimes, but the names do not prove the boundaries match. Map an endpoint to a business command only after identifying the state transition, recoverable writes, syncpoints, duplicate behavior, and result.

What happens to a COMMAREA between pseudo-conversational tasks?

CICS keeps the passed bytes and makes them available to the first program in the next task associated with the terminal. The COMMAREA carries continuation data, but it is not a recoverable database record.

Should a migrated application store COMMAREA data in an HTTP session?

Not as a blanket rule. Put durable business and workflow state in a versioned server-side record, recompute presentation data, and use signed client state only for values the client may safely hold and replay.

When does CICS commit work?

CICS commits recoverable changes at an explicit syncpoint or at the normal end of a top-level task. An abend normally backs out uncommitted recoverable changes in the current unit of work.

Does an HTTP 200 response mean the CICS work committed?

Only if the adapter constructs that response from a known committed outcome. A connection can fail after CICS commits, and a framework can prepare a response before its database transaction actually commits.

Can retries replace CICS two-phase commit?

No. Retries repeat an attempt; they do not coordinate one commit decision across prepared resource managers. Use one local transaction where possible, or design durable workflow states, idempotency, an outbox, and compensations.

How should duplicate HTTP requests be handled after migration?

Give each command a stable ID and store that ID, a request fingerprint, and the outcome in the same transaction as the business update. Return the recorded result for an exact duplicate and reject reused IDs with different content.

Are CICS channels and containers durable state?

No. They improve how programs pass structured data and avoid the single COMMAREA size constraint, but they do not replace a durable workflow record or change the commit semantics of business resources.

What should a CICS migration parity test compare?

Compare initial and final durable state, allowed transitions, outputs, resource effects, and duplicate results. Inject timeouts, abends, stale versions, and lost responses around syncpoints; clean request replay alone misses the expensive failures.