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

  1. Remove delay settings before the transactional send: rebuild the message without setDelayTimeLevel/setDelayTimeMs/setDeliverTimeMs.
  2. Split the workflow: send the transactional message first, and after commit send a separate delayed follow-up message if a timed action is needed.
  3. Audit shared message-construction helpers for unconditional delay properties.
  4. 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

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


AI-assisted analysis of apache/rocketmq@293f588571 (2026-08-14). Data as JSON: /api/errors/216cde35073c980b. Report an issue: GitHub.