Transactional messages are a type of featured message provided by ApsaraMQ for RocketMQ that guarantee a local transaction and a message delivery either both succeed or both fail. This two-phase commit mechanism keeps a core service and its downstream consumers in sync without the resource-locking overhead of eXtended Architecture (XA) distributed transactions.

Use transactional messages when:
An order system must update its database and notify the logistics, points, and cart services atomically.
A payment service must record a debit and publish an event to downstream ledger consumers.
Delivering a message without completing the local transaction (or vice versa) would leave the system in an inconsistent state.
How transactional messages work
Why normal messages fall short
Combining a local database transaction with a normal message send creates a gap where one can succeed without the other:
The message is sent, but the local transaction fails. Downstream consumers act on an uncommitted change.
The local transaction commits, but the message send fails. Downstream consumers never learn about the change.
A timeout occurs, and neither the producer nor the broker can determine whether to commit or roll back.

Why XA transactions are too costly
The XA protocol can coordinate distributed transactions across systems, but it locks resources for the entire duration of the transaction. As the number of participating systems grows, lock contention increases and throughput drops.
Two-phase commit with half messages
ApsaraMQ for RocketMQ transactional messages use a two-phase commit protocol that avoids both problems:

Send a half message. The producer sends a message to the broker. The broker persists it and returns an acknowledgment (ACK) to the producer. The message is marked as *not ready for delivery* -- a message in this state is called a half message. Downstream consumers cannot see it yet.
Run the local transaction. The producer runs its local database operation (for example, updating an order status from *unpaid* to *paid*).
Commit or roll back. The producer reports the local transaction result to the broker:
Commit: The broker marks the half message as *ready for delivery* and delivers it to consumers.
Rollback: The broker discards the half message. Consumers never receive it.
Transaction status check (recovery). If the broker does not receive a commit or rollback result -- due to a network failure or producer restart -- it sends a status query to a producer instance in the cluster. The producer checks the local transaction result and re-reports it to the broker.

For the query interval and maximum retry count, see Parameter limits.
Message lifecycle
A transactional message moves through the following states:

| State | Description |
|---|---|
| Initialization | The producer builds the half message and prepares to send it to the broker. |
| Transaction to be committed | The broker stores the half message in the transaction storage system. Unlike a normal message, the half message is not persisted by the broker in the standard way. The message is invisible to consumers. |
| Committed for consumption | The local transaction succeeds. The broker stores the half message in the storage system, making it visible to consumers. |
| Message rollback | The local transaction fails. The broker discards the half message. The workflow ends. |
| Being consumed | A consumer picks up the message and begins processing it. If the consumer does not return a result within the configured timeout, ApsaraMQ for RocketMQ retries delivery. For details, see Consumption retry. |
| Consumption result commit | The consumer commits the consumption result. The message is marked as consumed but not immediately deleted. |
| Message deletion | The message retention period expires or storage space runs low. ApsaraMQ for RocketMQ deletes the oldest messages in a rolling manner. See Message storage and cleanup. |
By default, ApsaraMQ for RocketMQ retains all messages. A consumed message is not deleted immediately -- consumers can re-consume it until the retention period expires or storage space is reclaimed.
Send transactional messages (Java)
Prerequisites
Before you begin, make sure that you have:
A topic with
MessageTypeset toTransactionin the ApsaraMQ for RocketMQ console.The instance endpoint (from the Endpoints tab of the Instance Details page)
(If applicable) The instance username and password (from the Intelligent Authentication tab of the Access Control page)
Differences from normal messages
Sending a transactional message differs from sending a normal message in two ways:
Transaction checker required. Register a transaction checker when building the producer. The checker runs automatically if the broker queries the transaction status after a failure.
Topic binding required. Bind the target topic to the producer at build time so the built-in checker can recover the transaction status.
Sample code
Build a producer with a transaction checker, begin a transaction, send a half message, run the local transaction, then commit or roll back.
Sample code
Replace the following placeholders with your actual values:
| Placeholder | Description | Example |
|---|---|---|
<your-instance-endpoint> | Instance endpoint (from the Endpoints tab of the Instance Details page) | xxx-hangzhou.rmq.aliyuncs.com:8080 |
<your-transaction-topic> | Topic name with MessageType set to Transaction | order-tx-topic |
<your-username> | Instance username (from the Intelligent Authentication tab of the Access Control page) | MjoxODgwNzcwODY5MD**** |
<your-password> | Instance password | NEh6cm9FVUl**** |
For complete SDK examples across all supported languages, see Apache RocketMQ 5.x SDKs.
Best practices
Minimize unknown transaction results
The transaction status check exists as a safety net for failures during commit or rollback. A high volume of status checks degrades system performance and delays message delivery. Design local transactions to return a definitive Commit or Rollback result as quickly as possible.
Handle in-progress transactions correctly
When the broker queries a half message status and the local transaction is still running, return Unknown -- not Commit or Rollback. Returning a premature result can cause data inconsistency.
If status checks arrive too early because the local transaction is slow, consider these approaches:
Increase the first check delay. Configure a longer interval before the broker sends its first status query. Trade-off: this also delays recovery for genuinely failed transactions.
Detect in-progress state explicitly. Design the local transaction logic to distinguish between "still running" and "failed" so the checker returns the correct status.
Limits
| Constraint | Details |
|---|---|
| Topic type | Transactional messages require a topic with MessageType set to Transaction. |
| One SendReceipt per transaction | Each transaction supports only one SendReceipt. |
| Eventual consistency only | Transactional messages guarantee consistency between the local transaction and message delivery. They do not guarantee real-time consistency across downstream consumers. Until the message is delivered, downstream state may lag behind the upstream transaction. Use transactional messages only when asynchronous downstream processing is acceptable. |
| Consumer-side responsibility | ApsaraMQ for RocketMQ guarantees that committed messages are delivered, but each downstream consumer must handle processing correctly. Implement consumption retry logic to handle transient failures. See Consumption retry. |
| Transaction timeout | If the broker cannot determine the transaction result after the configured timeout and maximum retry count, it rolls back the half message by default. See Parameter limits. |