apache/rocketmq · error · MQClientException

tranExecutor is null

Error message

tranExecutor is null

What it means

Thrown by sendMessageInTransaction when both the passed-in localTransactionListener is null and the producer's internal check listener (getCheckListener()) is null. At least one listener must exist: the local one executes the local transaction branch, the internal one answers broker transaction-status checks. With neither, the transactional protocol cannot proceed.

Source

Thrown at client/src/main/java/org/apache/rocketmq/client/impl/producer/DefaultMQProducerImpl.java:1438

    /**
     * SELECT ONEWAY -------------------------------------------------------
     */
    public void sendOneway(Message msg, MessageQueueSelector selector, Object arg)
        throws MQClientException, RemotingException, InterruptedException {
        try {
            this.sendSelectImpl(msg, selector, arg, CommunicationMode.ONEWAY, null, this.defaultMQProducer.getSendMsgTimeout());
        } catch (MQBrokerException e) {
            throw new MQClientException("unknown exception", e);
        }
    }

    public TransactionSendResult sendMessageInTransaction(final Message msg,
        final TransactionListener localTransactionListener, final Object arg)
        throws MQClientException {
        TransactionListener transactionListener = getCheckListener();
        if (null == localTransactionListener && null == transactionListener) {
            throw new MQClientException("tranExecutor is null", null);
        }

        ensureNotDelayedForTransactional(msg);
        Validators.checkMessage(msg, this.defaultMQProducer);

        SendResult sendResult = null;
        MessageAccessor.putProperty(msg, MessageConst.PROPERTY_TRANSACTION_PREPARED, "true");
        MessageAccessor.putProperty(msg, MessageConst.PROPERTY_PRODUCER_GROUP, this.defaultMQProducer.getProducerGroup());
        try {
            sendResult = this.send(msg);
        } catch (Exception e) {
            throw new MQClientException("send message Exception", e);
        }

        LocalTransactionState localTransactionState = LocalTransactionState.UNKNOW;
        Throwable localException = null;
        switch (sendResult.getSendStatus()) {
            case SEND_OK: {

View on GitHub (pinned to 293f588571)

Solutions

  1. Pass a non-null TransactionListener as the second argument of sendMessageInTransaction.
  2. Or, if you want broker-driven transaction checks, set the listener on the producer before start(): ((TransactionMQProducer) producer).setTransactionListener(listener).
  3. Fail fast at wiring time: assert the listener is non-null right after producer construction rather than at first send.
  4. Do not reuse one producer for both plain and transactional sends if the transactional listener wiring is conditional.

Example fix

// before
TransactionSendResult r = producer.sendMessageInTransaction(msg, null, arg);

// after
TransactionListener listener = new TransactionListener() {
    public LocalTransactionState executeLocalTransaction(Message m, Object a) { /* commit DB tx */ return LocalTransactionState.COMMIT_MESSAGE; }
    public LocalTransactionState checkLocalTransaction(MessageExt m) { return LocalTransactionState.COMMIT_MESSAGE; }
};
TransactionSendResult r = producer.sendMessageInTransaction(msg, listener, arg);
Defensive patterns

Strategy: validation

Validate before calling

Objects.requireNonNull(localTransactionListener,
    "TransactionListener required for sendMessageInTransaction");
// also assert producer wiring if you rely on check-backs
if (producer instanceof TransactionMQProducer tp
        && tp.getTransactionListener() == null && localTransactionListener == null) {
    throw new IllegalStateException("no transaction listener configured");
}

Try / catch

try {
    producer.sendMessageInTransaction(msg, listener, arg);
} catch (MQClientException e) {
    if ("tranExecutor is null".equals(e.getMessage())) {
        throw new IllegalStateException("transaction listener wiring missing", e); // config bug, fail fast
    }
    throw e;
}

Prevention

When it happens

Trigger: Calling producer.sendMessageInTransaction(msg, null, arg) on a DefaultMQProducer (or a subclass like TransactionMQProducer whose transactionListener was never set), so both listener references are null.

Common situations: Migrating code from TransactionMQProducer to DefaultMQProducer and dropping setTransactionListener; Spring bean wiring where the listener field is optional and silently null; forgetting that the check listener is only installed when a TransactionListener was configured before start().

Related errors


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