Explained: MQ on z/OS

Codelooru mq on zos

A customer submits a mortgage application on a bank's website. The web server accepts it immediately and shows a confirmation. Behind the scenes, that application needs to pass through a fraud check, a credit bureau lookup, an underwriting system, and a document management platform. Each of those systems runs on different infrastructure, at different speeds, and is maintained by different teams. The web server cannot wait for all of them. It cannot afford to fail if any one of them is temporarily unavailable.

This is the problem that messaging middleware solves. The web server puts the application data onto a queue and returns to the user. Each downstream system reads from the queue when it is ready, processes its part, and puts its result onto another queue. No system needs to know where the others are, what language they are written in, or whether they are currently running. The queue is the contract between them.

On z/OS, that middleware is IBM MQ. It is the standard integration layer between mainframe CICS applications, batch jobs, and the distributed systems around them. This post explains how MQ is structured on z/OS, how messages move between systems, and how CICS programs put and get messages. It assumes you have read Architecture: Mainframe and Architecture: CICS.


The Core Idea: Asynchronous Decoupling

MQ is built around one principle: the sender and the receiver of a message do not need to be available at the same time. A CICS transaction puts a message onto a queue and completes. Later, perhaps milliseconds later, perhaps minutes later if the downstream system is busy or restarting, a consumer reads the message and processes it. Neither side blocks on the other.

This is the opposite of a synchronous call. When a CICS program issues `EXEC CICS LINK` to another program, it waits. When it calls a web service over HTTP, it waits. Waiting is fine for short, predictable operations, but it creates fragility at scale: if the downstream system is slow or unavailable, the CICS task holds its resources and eventually times out. MQ breaks that dependency entirely.

The trade-off is complexity in the programming model. A program that puts a message cannot immediately read the response. If it needs a response, it must either poll a reply queue or be triggered when the response arrives. Designing for asynchrony requires thinking differently about how work flows through a system.


The Queue Manager

Every MQ installation revolves around a queue manager. The queue manager is the MQ server: the process that owns queues, accepts connections from applications, stores messages, and routes them between systems. On z/OS, a queue manager runs as two cooperating address spaces: the master address space (named by the queue manager name, e.g. `CSQ1`) and the channel initiator address space (named `CSQ1CHIN`). The master handles all local queue operations. The channel initiator manages all communication with remote queue managers over the network.

A z/OS LPAR typically runs one queue manager, though multiple queue managers on the same LPAR are possible. Each queue manager is identified by a name of up to four characters on z/OS (longer names are supported on distributed platforms). The queue manager name appears in every MQ command and in channel definitions: it is the primary address for routing messages between systems.

Applications connect to a queue manager using the MQI (Message Queue Interface): a set of API calls that are consistent across every platform MQ runs on. The core calls are:

  • MQCONN: connect to a queue manager.
  • MQOPEN: open a queue for put or get operations.
  • MQPUT: place a message onto a queue.
  • MQGET: retrieve a message from a queue.
  • MQCLOSE: close the queue.
  • MQDISC: disconnect from the queue manager.

In CICS, the MQI is accessed through the CICS-MQ adapter, which handles the connection between the CICS region and the queue manager. CICS programs use `EXEC CICS PUT CONTAINER` or direct MQI calls via a stub, depending on the integration style in use. Most commonly, CICS programs call the MQI directly using COBOL CALL statements to the MQ stub modules.

APPLICATIONS CICS programs Batch COBOL / JCL Java / JMS (remote) REST / z/OS Connect QUEUE MANAGER CSQ1 Master AS (CSQ1MSTR) Local queues PAYMENT.IN, FRAUD.OUT Dead-letter queue Undeliverable messages Log manager Persistent message log Page sets Queue storage on DASD Channel Initiator (CSQ1CHIN) Sender channels Push to remote QMs Receiver channels Pull from remote QMs Listener TCP/IP port for inbound Transmission queue Staging for remote msgs DASD — Page Sets (VSAM LDS) + Active and Archive Logs Persistent messages survive queue manager restart Figure 1: MQ queue manager on z/OS — two address spaces, local queues, channels, and DASD storage.

Queue Types

Not all queues in MQ are the same. Understanding the different types is essential to designing an MQ-based integration correctly.

Local Queue

A local queue is a real queue: it has physical storage and holds messages. It is owned by the local queue manager. Applications put messages onto and get messages from local queues directly. Every other queue type is ultimately a pointer to a local queue, either on the local queue manager or on a remote one.

Remote Queue Definition

A remote queue definition is a local object that acts as an alias for a queue on a remote queue manager. When an application puts a message to a remote queue definition, MQ routes the message through a channel to the target queue manager. The application does not need to know the remote queue manager's name or network address: it just opens the remote queue definition as if it were a local queue. The indirection means routing can change without changing the application.

Alias Queue

An alias queue is a local name that maps to another local queue on the same queue manager. It provides an abstraction layer: the application opens `PAYMENT.REQUEST` (the alias), and behind the scenes it reads from `PAYMENT.REQUEST.V2` (the real queue). When the underlying queue is renamed or replaced, only the alias definition changes, not the application.

Transmission Queue

A transmission queue is a staging area used by the channel initiator when sending messages to a remote queue manager. When an application puts a message destined for a remote queue, MQ first places it on the transmission queue for that remote queue manager. The channel initiator picks up messages from the transmission queue and transmits them over the network channel. The transmission queue acts as a buffer: if the channel is down, messages accumulate on the transmission queue until the channel recovers and transmits them.

Dead-Letter Queue

The dead-letter queue (DLQ) receives messages that cannot be delivered to their intended destination: the target queue is full, the target queue does not exist, or a channel cannot deliver the message after repeated attempts. Every queue manager has one DLQ, defined in the queue manager attributes. Messages on the DLQ include a dead-letter header (MQDLH) that records why the message was not delivered. The DLQ handler utility (`CSQUDLQH` on z/OS) processes the DLQ according to a rules file: retry delivery, move to another queue, discard, or raise an alert.


Messages and Persistence

An MQ message has two parts: the message descriptor (MQMD) and the message data. The MQMD is a fixed-format header containing metadata about the message: its unique message ID, a correlation ID (used to match replies to requests), the message type, priority, the reply-to queue name, and crucially the persistence setting.

A non-persistent message is held only in memory. If the queue manager restarts for any reason, non-persistent messages are lost. This is acceptable for high-volume, low-value data where speed matters more than guaranteed delivery: real-time market data feeds, status updates, or notifications where losing an occasional message has no business impact.

A persistent message is written to the MQ log and to the page set (disk storage) before the put operation returns to the application. If the queue manager restarts, persistent messages survive and are recovered from the log. The trade-off is I/O overhead: every persistent put involves a synchronous write to the log. For critical business messages — payment instructions, order confirmations, trade executions — persistence is non-negotiable.

The MQMD also carries a priority value from 0 to 9. Higher priority messages are returned first on an MQGET, regardless of arrival order. Most production queues use a single priority, but the mechanism exists for workloads where some messages need to jump the queue.


Channels

A channel is a one-directional communication link between two queue managers. It has a sender end and a receiver end: messages flow in one direction only. To send messages in both directions between two queue managers, two channels are defined: one in each direction.

A channel is not a permanent connection. It starts when there are messages to send (or when triggered manually) and stops after a configurable idle period. The sender channel reads messages from the transmission queue and transmits them over TCP/IP to the receiver channel on the remote queue manager, which places them on the target local queue.

Channels have retry logic built in. If the network connection fails mid-transmission, the channel retries according to a configurable retry interval and count. Persistent messages that were in flight when the failure occurred are retransmitted: MQ guarantees that a persistent message is delivered exactly once, even through network failures, because the sender does not remove the message from the transmission queue until it receives an acknowledgment from the receiver.

On z/OS, the channel initiator address space manages all channel activity. Each active channel runs as a task within the channel initiator. The number of concurrent channels is bounded by the channel initiator's capacity, which is a tuning parameter.


Transactional Messaging

MQ operations can participate in a transaction, coordinated either locally (by the queue manager) or globally (by a two-phase commit coordinator such as CICS via RRS). This is one of MQ's most important features for mainframe integration.

When a CICS transaction puts a message under syncpoint, the message is not visible to other applications until the CICS transaction commits. If the CICS transaction rolls back, the message put is also rolled back: the message disappears from the queue as if it was never put. Similarly, when a CICS transaction gets a message under syncpoint, the message is not removed from the queue until the transaction commits. If the transaction rolls back, the message is returned to the queue and can be processed again.

This coordination is what makes MQ safe for financial transactions. A CICS program that debits an account and puts a payment instruction onto an MQ queue is doing both operations under the same unit of work. Either both commit together or both roll back together. There is no scenario where the account is debited but the payment instruction is lost, or where the payment instruction is sent but the debit fails.

The coordination between CICS and MQ on z/OS goes through RRS (Resource Recovery Services), the same z/OS component that coordinates CICS and DB2 transactions. RRS drives the two-phase commit across both CICS and MQ, ensuring that both resources are in the same committed or rolled-back state.

CICS Task Debit account (VSAM) MQPUT (under syncpt) Message not visible yet EXEC CICS RETURN Auto syncpoint RRS Two-phase commit coordinator COMMIT VSAM update permanent MQ message now visible Success ROLLBACK VSAM update reversed MQ message discarded Failure Figure 2: Transactional MQ put under CICS syncpoint — commit and rollback are atomic across VSAM and MQ.

Triggering

A consumer program does not have to poll a queue continuously to process messages. MQ supports triggering: when a message arrives on a queue, MQ automatically starts the consumer application to process it.

To use triggering, a queue is defined with a trigger attribute and linked to an initiation queue. When the trigger condition is met (a message arrives, the queue reaches a specified depth, or the first message arrives after the queue was empty), MQ puts a trigger message onto the initiation queue. A trigger monitor program watches the initiation queue and starts the consumer application when it sees the trigger message.

In CICS, the trigger monitor is the `CKTI` transaction (CICS MQ Trigger Monitor). `CKTI` runs continuously, watching the initiation queue. When a trigger message arrives, `CKTI` starts the CICS transaction specified in the queue definition, passing the trigger information. The triggered transaction reads and processes the message, then terminates. `CKTI` then waits for the next trigger.

Triggering decouples producers from consumers completely. A batch job finishing overnight can put results onto a queue; the CICS consumer transaction is triggered automatically and processes the results without any scheduling or polling logic in either program. This is the cleanest integration pattern for batch-to-online handoffs in a mainframe environment.


Queue Sharing Groups on z/OS

On a Parallel Sysplex, MQ can be configured in a Queue Sharing Group (QSG): multiple queue managers, each running on a separate LPAR, that share access to the same queues via the Coupling Facility.

In a QSG, shared queues are stored in CF list structures rather than on any individual queue manager's page sets. Any queue manager in the group can put to or get from any shared queue. If one queue manager fails, the others continue processing messages from the shared queue without interruption. This is MQ's high-availability model on z/OS: it mirrors the Parallel Sysplex model used by CICS and DB2.

A CICS region connects to its queue manager by name, but when using a QSG it specifies the group name instead. CICS then connects to whichever queue manager in the group is available. If the connected queue manager fails, CICS reconnects to another member automatically. Combined with CICS's own CICSPlex topology, this produces an active-active architecture where both CICS transactions and MQ message processing survive individual system failures.


The Dead-Letter Queue in Practice

The DLQ is a safety net but also a diagnostic signal. Messages end up there for specific reasons, recorded in the MQDLH header: `MQRC_Q_FULL` (target queue was full), `MQRC_NOT_AUTHORIZED` (the channel's identity did not have put authority on the target queue), `MQRC_UNKNOWN_OBJECT_NAME` (the target queue does not exist). Monitoring DLQ depth is therefore a first-line operational indicator: a growing DLQ means something upstream is misconfigured or a downstream consumer is not keeping up.

The DLQ handler runs as a batch job on z/OS. Its rules file specifies what to do with each category of message: retry delivery after a delay, forward to a different queue, discard with a log entry, or raise an operator alert. A well-maintained DLQ handler prevents the DLQ from filling up (which would itself cause messages to be lost) and ensures that undeliverable messages are investigated rather than silently discarded.


A CICS Program Putting a Message

Here is how a CICS COBOL program puts a payment instruction onto an MQ queue. The MQI calls are COBOL CALL statements to MQ stub modules, not `EXEC CICS` commands. The program uses the syncpoint-aware put to ensure the message is tied to the CICS unit of work.

WORKING-STORAGE SECTION.
01 MQ-HCONN            PIC S9(9) COMP.
01 MQ-HOBJ             PIC S9(9) COMP.
01 MQ-COMP-CODE        PIC S9(9) COMP.
01 MQ-REASON           PIC S9(9) COMP.
01 MQ-OPEN-OPTIONS     PIC S9(9) COMP.
01 MQ-PUT-OPTIONS      PIC S9(9) COMP.
01 MQ-OD               PIC X(1200).
01 MQ-MD               PIC X(364).
01 MQ-PMO              PIC X(196).

PROCEDURE DIVISION.
PUT-PAYMENT-MESSAGE.
    *> Connect to the queue manager (usually done once at task start)
    CALL 'MQCONN'
        USING MQ-QM-NAME
              MQ-HCONN
              MQ-COMP-CODE
              MQ-REASON

    *> Open the payment queue for put
    MOVE MQOO-OUTPUT TO MQ-OPEN-OPTIONS
    CALL 'MQOPEN'
        USING MQ-HCONN
              MQ-OD
              MQ-OPEN-OPTIONS
              MQ-HOBJ
              MQ-COMP-CODE
              MQ-REASON

    *> Set syncpoint option so put is tied to CICS UOW
    MOVE MQPMO-SYNCPOINT TO MQ-PUT-OPTIONS

    *> Put the payment message
    CALL 'MQPUT'
        USING MQ-HCONN
              MQ-HOBJ
              MQ-MD
              MQ-PMO
              WS-MSG-LENGTH
              WS-PAYMENT-MESSAGE
              MQ-COMP-CODE
              MQ-REASON

    IF MQ-COMP-CODE NOT = MQCC-OK
        PERFORM HANDLE-MQ-ERROR
    END-IF

    *> Close the queue
    CALL 'MQCLOSE'
        USING MQ-HCONN
              MQ-HOBJ
              MQCO-NONE
              MQ-COMP-CODE
              MQ-REASON

    *> Message becomes visible only when CICS commits
    EXEC CICS RETURN END-EXEC.

The key line is `MOVE MQPMO-SYNCPOINT TO MQ-PUT-OPTIONS`. This tells MQ to hold the message under the current syncpoint rather than making it immediately visible. When `EXEC CICS RETURN` triggers the automatic CICS syncpoint, RRS drives the two-phase commit: MQ commits the message and CICS commits all its own changes atomically.


Where MQ Fits in the Mainframe Architecture

MQ sits at the boundary between the synchronous world of CICS transactions and the asynchronous world of distributed systems. It connects things that cannot or should not be directly coupled: a CICS payment transaction and a Linux-based fraud scoring service; a batch job producing overnight results and a CICS online inquiry transaction; a mainframe insurance system and a cloud-based document management platform.

Within the mainframe itself, MQ provides a reliable handoff between CICS regions, between CICS and batch, and between batch jobs that run in different windows. The Coupling Facility-backed QSG model means that handoffs survive individual system failures without message loss. Combined with transactional messaging under CICS syncpoint, MQ provides the same "exactly once" delivery guarantee for cross-system message passing that CICS provides for in-transaction file updates.


Summary

MQ decouples producers from consumers. A program puts a message onto a queue and returns immediately, without waiting for anything downstream. The queue manager stores the message until a consumer is ready. If the message is persistent and the put is under syncpoint, the message is guaranteed to be delivered exactly once, even through system failures, network outages, and queue manager restarts.

On z/OS, the queue manager runs as two address spaces: the master for local queue operations and the channel initiator for inter-system communication. Queue sharing groups extend this across a Parallel Sysplex, giving MQ the same high-availability model as CICS and DB2. Triggering eliminates polling by starting consumer transactions automatically when messages arrive. The dead-letter queue catches undeliverable messages and provides the diagnostic signal needed to find and fix integration failures.

The integration between MQ and CICS, coordinated by RRS through two-phase commit, is what makes asynchronous messaging safe for financial transactions. A CICS task that debits an account and puts a payment instruction commits both atomically, or rolls back both. The message and the data are always consistent, regardless of what fails in between.

Part of the Mainframe Decoded series — IBM Z and z/OS, clearly explained for engineers.



×