apache/rocketmq · error · MQClientException
topic is not supported
Error message
topic is not supported
What it means
Thrown by recallMessage(topic, recallHandle) when the topic is a retry topic (%RETRY%...) or DLQ topic (%DLQ%...) as detected by NamespaceUtil. Recall (message retraction) is only meaningful for user topics; retry/DLQ topics are system-managed internal streams whose contents the recall protocol does not support, so the client rejects them before decoding the handle.
Source
Thrown at client/src/main/java/org/apache/rocketmq/client/impl/producer/DefaultMQProducerImpl.java:1576
break;
}
doExecuteEndTransactionHook(msg, sendResult.getMsgId(), brokerAddr, localTransactionState, false);
requestHeader.setProducerGroup(this.defaultMQProducer.getProducerGroup());
requestHeader.setTranStateTableOffset(sendResult.getQueueOffset());
requestHeader.setMsgId(sendResult.getMsgId());
String remark = localException != null ? ("executeLocalTransactionBranch exception: " + localException.toString()) : null;
this.mQClientFactory.getMQClientAPIImpl().endTransactionOneway(brokerAddr, requestHeader, remark,
this.defaultMQProducer.getSendMsgTimeout());
}
public String recallMessage(
String topic,
String recallHandle) throws RemotingException, MQClientException, MQBrokerException, InterruptedException {
makeSureStateOK();
Validators.checkTopic(topic);
if (NamespaceUtil.isRetryTopic(topic) || NamespaceUtil.isDLQTopic(topic)) {
throw new MQClientException("topic is not supported", null);
}
RecallMessageHandle.HandleV1 handleEntity;
try {
handleEntity = (RecallMessageHandle.HandleV1) RecallMessageHandle.decodeHandle(recallHandle);
} catch (Exception e) {
throw new MQClientException(e.getMessage(), null);
}
tryToFindTopicPublishInfo(topic);
String brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(handleEntity.getBrokerName());
brokerAddr = StringUtils.isNotEmpty(brokerAddr) ?
// find another address to support multi proxy endpoints,
// may cause failure request in proxy-less mode when the broker is temporarily unavailable
brokerAddr : this.mQClientFactory.findBrokerAddrByTopic(topic);
if (StringUtils.isEmpty(brokerAddr)) {
log.warn("can't find broker service address. {}", handleEntity.getBrokerName());
throw new MQClientException("The broker service address not found", null);
}View on GitHub (pinned to 293f588571)
Solutions
- Recall the original user topic instead — the topic the message was originally published to (available from the message's real topic field, not its retry/DLQ topic).
- Add a UI/API guard that rejects %RETRY% and %DLQ% topics before invoking recallMessage.
- If operating with namespaces, pass the topic without the system prefix and let client config apply the namespace.
Example fix
// before
producer.recallMessage(msgExt.getTopic(), handle); // topic may be %RETRY%grp
// after
String realTopic = NamespaceUtil.isRetryTopic(msgExt.getTopic()) || NamespaceUtil.isDLQTopic(msgExt.getTopic())
? msgExt.getProperty(MessageConst.PROPERTY_REAL_TOPIC) : msgExt.getTopic();
producer.recallMessage(realTopic, handle); Defensive patterns
Strategy: validation
Validate before calling
if (NamespaceUtil.isRetryTopic(topic) || NamespaceUtil.isDLQTopic(topic)) {
throw new IllegalArgumentException("cannot recall system topic: " + topic);
}
producer.recallMessage(topic, handle); Type guard
static boolean isRecallableTopic(String topic) {
return topic != null && !NamespaceUtil.isRetryTopic(topic) && !NamespaceUtil.isDLQTopic(topic);
} Try / catch
try {
producer.recallMessage(topic, handle);
} catch (MQClientException e) {
if ("topic is not supported".equals(e.getMessage())) {
throw new IllegalArgumentException("use the original user topic for recall, got: " + topic, e);
}
throw e;
} Prevention
- Validate topic names at the UI/API boundary before exposing recall.
- Store the original topic alongside recall handles so the right value is always available.
- Remember retry/DLQ topics are system-managed: never target them with producer features.
When it happens
Trigger: Calling producer.recallMessage with a topic string that starts with %RETRY% or %DLQ% (possibly introduced by a namespace prefix), or passing a consumer-group retry topic as the recall target.
Common situations: UI/tooling that lets users paste any topic name for recall; retry-topic constants accidentally wired into the recall path; namespace wrapping that turns a normal name into a system-prefixed one on the client side.
Related errors
- Sending message to topic[%s] is forbidden.
- 13
- 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/7cb80d7707e97827.
Report an issue: GitHub.