apache/rocketmq · error · MQClientException
Transactional messages do not support delayed delivery
Error message
Transactional messages do not support delayed delivery
What it means
Thrown by ensureNotDelayedForTransactional when a message submitted to sendMessageInTransaction carries any delay property: PROPERTY_DELAY_TIME_LEVEL, PROPERTY_TIMER_DELAY_MS, PROPERTY_TIMER_DELAY_SEC, or PROPERTY_TIMER_DELIVER_MS. RocketMQ's transactional protocol requires the half message to be consumable/committable immediately, which conflicts with delayed/timer-based delivery, so the client rejects the combination up front.
Source
Thrown at client/src/main/java/org/apache/rocketmq/client/impl/producer/DefaultMQProducerImpl.java:1516
log.warn("local transaction execute {}, but end broker transaction failed", localTransactionState, e);
}
TransactionSendResult transactionSendResult = new TransactionSendResult();
transactionSendResult.setSendStatus(sendResult.getSendStatus());
transactionSendResult.setMessageQueue(sendResult.getMessageQueue());
transactionSendResult.setMsgId(sendResult.getMsgId());
transactionSendResult.setQueueOffset(sendResult.getQueueOffset());
transactionSendResult.setTransactionId(sendResult.getTransactionId());
transactionSendResult.setLocalTransactionState(localTransactionState);
return transactionSendResult;
}
private void ensureNotDelayedForTransactional(final Message msg) throws MQClientException {
if (msg.getProperty(MessageConst.PROPERTY_DELAY_TIME_LEVEL) != null
|| msg.getProperty(MessageConst.PROPERTY_TIMER_DELAY_MS) != null
|| msg.getProperty(MessageConst.PROPERTY_TIMER_DELAY_SEC) != null
|| msg.getProperty(MessageConst.PROPERTY_TIMER_DELIVER_MS) != null) {
throw new MQClientException("Transactional messages do not support delayed delivery", null);
}
}
/**
* DEFAULT SYNC -------------------------------------------------------
*/
public SendResult send(
Message msg) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
return send(msg, this.defaultMQProducer.getSendMsgTimeout());
}
public void endTransaction(
final Message msg,
final SendResult sendResult,
final LocalTransactionState localTransactionState,
final Throwable localException) throws RemotingException, MQBrokerException, InterruptedException, UnknownHostException {
final MessageId id;
if (sendResult.getOffsetMsgId() != null) {View on GitHub (pinned to 293f588571)
Solutions
- Remove delay settings before the transactional send: rebuild the message without setDelayTimeLevel/setDelayTimeMs/setDeliverTimeMs.
- Split the workflow: send the transactional message first, and after commit send a separate delayed follow-up message if a timed action is needed.
- Audit shared message-construction helpers for unconditional delay properties.
- If a 'commit after delay' pattern is required, implement it with a timer topic or scheduler service, not by combining the two features.
Example fix
// before
Message msg = new Message(topic, body);
msg.setDelayTimeLevel(3);
producer.sendMessageInTransaction(msg, listener, arg);
// after
Message msg = new Message(topic, body);
TransactionSendResult r = producer.sendMessageInTransaction(msg, listener, arg);
if (r.getLocalTransactionState() == LocalTransactionState.COMMIT_MESSAGE) {
Message delayed = new Message(topic, body);
delayed.setDelayTimeLevel(3);
producer.send(delayed); // delayed follow-up, separate from the tx
} Defensive patterns
Strategy: validation
Validate before calling
static void assertSendableAsTransactional(Message msg) {
for (String p : new String[]{"DELAY", MessageConst.PROPERTY_TIMER_DELAY_MS,
MessageConst.PROPERTY_TIMER_DELAY_SEC, MessageConst.PROPERTY_TIMER_DELIVER_MS}) {
if (msg.getProperty(p) != null)
throw new IllegalArgumentException("delay property " + p + " not allowed on transactional message");
}
} Try / catch
try {
producer.sendMessageInTransaction(msg, listener, arg);
} catch (MQClientException e) {
if (e.getMessage().contains("do not support delayed delivery")) {
Message plain = cloneWithoutDelay(msg);
producer.sendMessageInTransaction(plain, listener, arg);
} else throw e;
} Prevention
- Never route messages built by a delay-applying builder into transactional sends.
- Centralize transactional message construction in one factory that forbids delay setters.
- Implement 'delayed commit' workflows as a delayed follow-up message after transaction success.
When it happens
Trigger: Setting msg.setDelayTimeLevel(n) (or the timer delay properties via setDelayTimeMs/setDeliverTimeMs/user properties) and then passing that message to sendMessageInTransaction.
Common situations: A shared message-builder utility that always applies a delay, reused for transactional sends; migrating legacy delay-level code onto the request-reply/transaction API; setting '__TIMER_DELIVER_MS' as a user property manually.
Related errors
- 13
- Sending message to topic[%s] is forbidden.
- producerGroup can not equal {defaultProducerGroup}, please s
- message's topic not equal mq's topic
- call timeout
AI-assisted analysis of apache/rocketmq@293f588571 (2026-08-14).
Data as JSON: /api/errors/216cde35073c980b.
Report an issue: GitHub.