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

  1. 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).
  2. Add a UI/API guard that rejects %RETRY% and %DLQ% topics before invoking recallMessage.
  3. 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

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


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