apache/rocketmq · error · MQClientException
Sending message to topic[%s] is forbidden.
Error message
Sending message to topic[%s] is forbidden.
What it means
Validators.isNotAllowedSendTopic throws when the topic is on TopicValidator's forbidden-to-send list — %RETRY%* and %DLQ%* topics. Retry and DLQ topics are consumed, never published to directly; sending would corrupt retry semantics, so the client blocks it.
Source
Thrown at client/src/main/java/org/apache/rocketmq/client/Validators.java:121
}
if (isTopicOrGroupIllegal(topic)) {
throw new MQClientException(String.format(
"The specified topic[%s] contains illegal characters, allowing only %s", topic,
"^[%|a-zA-Z0-9_-]+$"), null);
}
}
public static void isSystemTopic(String topic) throws MQClientException {
if (TopicValidator.isSystemTopic(topic)) {
throw new MQClientException(
String.format("The topic[%s] is conflict with system topic.", topic), null);
}
}
public static void isNotAllowedSendTopic(String topic) throws MQClientException {
if (TopicValidator.isNotAllowedSendTopic(topic)) {
throw new MQClientException(
String.format("Sending message to topic[%s] is forbidden.", topic), null);
}
}
public static void checkTopicConfig(final TopicConfig topicConfig) throws MQClientException {
if (!PermName.isValid(topicConfig.getPerm())) {
throw new MQClientException(ResponseCode.NO_PERMISSION,
String.format("topicPermission value: %s is invalid.", topicConfig.getPerm()));
}
}
public static void checkBrokerConfig(final Properties brokerConfig) throws MQClientException {
String brokerPermission = brokerConfig.getProperty("brokerPermission");
if (brokerPermission != null && !PermName.isValid(brokerPermission)) {
throw new MQClientException(ResponseCode.NO_PERMISSION,
String.format("brokerPermission value: %s is invalid.", brokerPermission));
}
}View on GitHub (pinned to 293f588571)
Solutions
- Send to your own business topic; to re-drive failures, resend the original message to its original topic.
- Consume DLQ with a consumer subscribed to '%DLQ%group' instead of producing to it.
- For delayed retry semantics, rely on the consumer's suspendCurrentQueueTimeMillis/retryTimesWhenConsumeFailed or schedule a delayed message to the original topic.
Example fix
// before
producer.send(new Message("%DLQ%order-group", tag, body)); // forbidden
// after
producer.send(new Message("order-topic", tag, body)); // resend to original topic Defensive patterns
Strategy: validation
Validate before calling
static boolean sendableTopic(String topic) {
return !org.apache.rocketmq.common.topic.TopicValidator.isNotAllowedSendTopic(topic);
} Prevention
- Never produce into %RETRY%/%DLQ% topics; consume DLQ via subscription instead.
- For manual re-drive, resend the original payload to the original topic.
- Add a send-wrapper guard that rejects retry/DLQ topics with a clear domain error.
When it happens
Trigger: producer.send(new Message("%DLQ%order-group", ...)) or sending to '%RETRY%consumer-group'; checkMessage calls isNotAllowedSendTopic for every outbound message.
Common situations: Dead-letter handling reimplemented by writing to the DLQ topic manually; consumers that 'requeue' by producing into the retry topic; config that accidentally prefixes a business topic with %RETRY%.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- topic is not supported
- 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/5232a15d10e55e72.
Report an issue: GitHub.