{"record":{"id":"7cb80d7707e97827","repo":"apache/rocketmq","slug":"topic-is-not-supported","errorCode":null,"errorMessage":"topic is not supported","messagePattern":"topic is not supported","errorType":"validation","errorClass":"MQClientException","httpStatus":null,"severity":"error","filePath":"client/src/main/java/org/apache/rocketmq/client/impl/producer/DefaultMQProducerImpl.java","lineNumber":1576,"sourceCode":"                break;\n        }\n\n        doExecuteEndTransactionHook(msg, sendResult.getMsgId(), brokerAddr, localTransactionState, false);\n        requestHeader.setProducerGroup(this.defaultMQProducer.getProducerGroup());\n        requestHeader.setTranStateTableOffset(sendResult.getQueueOffset());\n        requestHeader.setMsgId(sendResult.getMsgId());\n        String remark = localException != null ? (\"executeLocalTransactionBranch exception: \" + localException.toString()) : null;\n        this.mQClientFactory.getMQClientAPIImpl().endTransactionOneway(brokerAddr, requestHeader, remark,\n            this.defaultMQProducer.getSendMsgTimeout());\n    }\n\n    public String recallMessage(\n        String topic,\n        String recallHandle) throws RemotingException, MQClientException, MQBrokerException, InterruptedException {\n        makeSureStateOK();\n        Validators.checkTopic(topic);\n        if (NamespaceUtil.isRetryTopic(topic) || NamespaceUtil.isDLQTopic(topic)) {\n            throw new MQClientException(\"topic is not supported\", null);\n        }\n        RecallMessageHandle.HandleV1 handleEntity;\n        try {\n            handleEntity = (RecallMessageHandle.HandleV1) RecallMessageHandle.decodeHandle(recallHandle);\n        } catch (Exception e) {\n            throw new MQClientException(e.getMessage(), null);\n        }\n\n        tryToFindTopicPublishInfo(topic);\n        String brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(handleEntity.getBrokerName());\n        brokerAddr = StringUtils.isNotEmpty(brokerAddr) ?\n            // find another address to support multi proxy endpoints,\n            // may cause failure request in proxy-less mode when the broker is temporarily unavailable\n            brokerAddr : this.mQClientFactory.findBrokerAddrByTopic(topic);\n        if (StringUtils.isEmpty(brokerAddr)) {\n            log.warn(\"can't find broker service address. {}\", handleEntity.getBrokerName());\n            throw new MQClientException(\"The broker service address not found\", null);\n        }","sourceCodeStart":1558,"sourceCodeEnd":1594,"githubUrl":"https://github.com/apache/rocketmq/blob/293f5885719fc4aa3619446a1900f58ccfcfdd29/client/src/main/java/org/apache/rocketmq/client/impl/producer/DefaultMQProducerImpl.java#L1558-L1594","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nproducer.recallMessage(msgExt.getTopic(), handle); // topic may be %RETRY%grp\n\n// after\nString realTopic = NamespaceUtil.isRetryTopic(msgExt.getTopic()) || NamespaceUtil.isDLQTopic(msgExt.getTopic())\n    ? msgExt.getProperty(MessageConst.PROPERTY_REAL_TOPIC) : msgExt.getTopic();\nproducer.recallMessage(realTopic, handle);","handlingStrategy":"validation","validationCode":"if (NamespaceUtil.isRetryTopic(topic) || NamespaceUtil.isDLQTopic(topic)) {\n    throw new IllegalArgumentException(\"cannot recall system topic: \" + topic);\n}\nproducer.recallMessage(topic, handle);","typeGuard":"static boolean isRecallableTopic(String topic) {\n    return topic != null && !NamespaceUtil.isRetryTopic(topic) && !NamespaceUtil.isDLQTopic(topic);\n}","tryCatchPattern":"try {\n    producer.recallMessage(topic, handle);\n} catch (MQClientException e) {\n    if (\"topic is not supported\".equals(e.getMessage())) {\n        throw new IllegalArgumentException(\"use the original user topic for recall, got: \" + topic, e);\n    }\n    throw e;\n}","preventionTips":["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."],"tags":["rocketmq","producer","message-recall","retry-topic","dlq","validation"],"backgroundTag":null,"analyzedSha":"293f5885719fc4aa3619446a1900f58ccfcfdd29","analyzedAt":"2026-08-14T11:50:13.822Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}