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
- Pass a non-null TransactionListener as the second argument of sendMessageInTransaction.
- Or, if you want broker-driven transaction checks, set the listener on the producer before start(): ((TransactionMQProducer) producer).setTransactionListener(listener).
- Fail fast at wiring time: assert the listener is non-null right after producer construction rather than at first send.
- 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
- Set the TransactionListener at producer construction time, never conditionally.
- Assert listener presence in a startup self-check so wiring bugs surface at boot, not first send.
- Keep transactional producers dedicated; don't share them with plain-send code paths.
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
- producerGroup can not equal {defaultProducerGroup}, please s
- send message Exception
- Transactional messages do not support delayed delivery
- The authenticationMetadataProvider is not configured
- The authenticationMetadataProvider is not configured.
AI-assisted analysis of apache/rocketmq@293f588571 (2026-08-14).
Data as JSON: /api/errors/a6b5202838036fa2.
Report an issue: GitHub.