Idempotency is how you live peacefully with IBM MQ at-least-once delivery: the same message—or two messages with the same business meaning—can arrive more than once, but your ledger, inventory, and customer accounts reflect the truth once. Banks call it duplicate detection; architects call it exactly-once processing built on at-least-once transport. Without idempotency, a crash between database commit and MQCMIT creates double charges; without business-key checks, a producer retry creates twin shipments. This page explains idempotent versus non-idempotent operations, MsgId and natural-key deduplication, status-column patterns, transactional outbox on the producer side, HTTP-style idempotency keys in payloads, poison message interaction, and testing strategies beginners can run in a lab before production traffic arrives.
Syncpoint consumers MQBACK on failure; the message returns. A network blip after your database updated but before MQCMIT produces the same scenario. Channels restart and redeliver. Batch reprocessing replays files. MQ is doing reliable messaging; your code must assume duplicates are normal, not exceptional. Idempotency is not optional for money movement—it is part of the contract.
| Operation | Idempotent? | Why |
|---|---|---|
| SET status=SHIPPED for order 42 | Yes (same end state) | Repeating leaves status SHIPPED |
| ADD 10 to balance | No | Twice adds 20 unless guarded |
| INSERT payment IF NOT EXISTS key | Yes | Second insert fails unique constraint |
| Append-only log line per MsgId | Yes if keyed | Same MsgId does not append twice |
Create PROCESSED_MESSAGES with columns MSG_ID_BINARY, BUSINESS_KEY, PROCESSED_AT, STATUS. On get, insert within the same database transaction as business logic, or insert first with unique constraint on MSG_ID or BUSINESS_KEY. Duplicate key violation means another worker already handled it—commit MQ without repeating side effects. Choose transaction boundaries so insert and business update commit together with MQCMIT when using XA.
ORDERS table: status NEW, PROCESSING, DONE. Transition NEW to PROCESSING with optimistic lock (UPDATE WHERE status=NEW). Only one consumer wins. Second consumer sees DONE or PROCESSING and skips. Clearer for auditors than silent no-op. Document allowed transitions to prevent illegal states.
Before MQPUT, check whether event already published. Transactional outbox: write OUTBOX row and business row in one DB transaction; relay process puts to MQ once and marks SENT. Prevents duplicate puts from application retries. Client-supplied idempotency keys in API headers should map to dedup store at the edge before messages enter MQ.
Idempotency does not fix poison—a message that always throws before commit will never reach DONE. BOTHRESH moves it to backout. Do not mark poison as processed in dedup table unless you intentionally skip bad data with approval. Separate quarantine workflow from duplicate skip workflow.
123456789-- Within same UoW as business update (conceptual): INSERT INTO processed_messages (msg_id, order_id, status) VALUES (:MsgId, :OrderId, 'DONE'); -- unique(msg_id) OR unique(order_id) -- On duplicate key: -- MQCMIT without changing balances again -- On success path: -- apply business rules, MQCMIT
The teacher asks you to write your name on the attendance sheet. If you accidentally go to the desk twice, you check—your name is already there, so you do not write it again. The sheet looks the same as if you went once. That is idempotent.
Wire transfer message may redeliver. Sketch tables and commit order with MQGET syncpoint.
Change ADD 10 to balance into an idempotent pattern using event id.
How do you test idempotency in CI—duplicate put, duplicate get, or both?
1. Idempotent processing means:
2. Unique index on order_id in processed_orders helps:
3. MQ redelivery of the same message usually keeps:
4. Increment account balance by 10 twice is NOT idempotent unless: