{"record":{"id":"83a519e5dfaadc63","repo":"apache/rocketmq","slug":"mapping-topic-does-not-match-topic-header","errorCode":null,"errorMessage":"mapping topic does not match topic header.","messagePattern":"mapping topic does not match topic header\\.","errorType":"exception","errorClass":"AuthorizationException","httpStatus":null,"severity":"error","filePath":"auth/src/main/java/org/apache/rocketmq/auth/authorization/builder/DefaultAuthorizationContextBuilder.java","lineNumber":534,"sourceCode":"                            throw new AuthorizationException(\"subscription group config is null.\");\n                        }\n                        addUniqueContext(result, subscriptionGroupResources, subject,\n                            Resource.ofGroup(requireResource(groupConfig.getGroupName(), \"consumer group\")),\n                            Action.CREATE, sourceIp);\n                    }\n                    break;\n                case RequestCode.UPDATE_AND_CREATE_STATIC_TOPIC:\n                    CreateTopicRequestHeader createTopicRequestHeader =\n                        command.decodeCommandCustomHeader(CreateTopicRequestHeader.class);\n                    if (createTopicRequestHeader == null) {\n                        throw new AuthorizationException(\"topic header is null.\");\n                    }\n                    String staticTopic = requireResource(createTopicRequestHeader.getTopic(), \"topic\");\n                    TopicQueueMappingDetail mappingDetail = decodeRequiredBody(\n                        command, TopicQueueMappingDetail.class, \"topic queue mapping\");\n                    if (!StringUtils.equals(\n                        staticTopic, requireResource(mappingDetail.getTopic(), \"mapping topic\"))) {\n                        throw new AuthorizationException(\"mapping topic does not match topic header.\");\n                    }\n                    topic = Resource.ofTopic(staticTopic);\n                    result.add(DefaultAuthorizationContext.of(subject, topic, Action.CREATE, sourceIp));\n                    break;\n                case RequestCode.GET_BROKER_CONFIG:\n                case RequestCode.GET_BROKER_RUNTIME_INFO:\n                case RequestCode.GET_ALL_CONSUMER_OFFSET:\n                case RequestCode.GET_TIMER_CHECK_POINT:\n                case RequestCode.GET_ALL_DELAY_OFFSET:\n                case RequestCode.GET_BROKER_HA_STATUS:\n                case RequestCode.GET_BROKER_EPOCH_CACHE:\n                case RequestCode.GET_BROKER_LITE_INFO:\n                    result.add(DefaultAuthorizationContext.of(subject,\n                        Resource.ofCluster(authConfig.getClusterName()), Action.GET, sourceIp));\n                    break;\n                case RequestCode.GET_ALL_TOPIC_CONFIG:\n                case RequestCode.GET_TIMER_METRICS:\n                case RequestCode.GET_SYSTEM_TOPIC_LIST_FROM_BROKER:","sourceCodeStart":516,"sourceCodeEnd":552,"githubUrl":"https://github.com/apache/rocketmq/blob/293f5885719fc4aa3619446a1900f58ccfcfdd29/auth/src/main/java/org/apache/rocketmq/auth/authorization/builder/DefaultAuthorizationContextBuilder.java#L516-L552","documentation":"Thrown for UPDATE_AND_CREATE_STATIC_TOPIC when the topic in the CreateTopicRequestHeader does not equal the topic inside the TopicQueueMappingDetail body. Both are required resources and the builder cross-checks them (StringUtils.equals) so a client cannot get a CREATE authorized for topic A while actually registering mapping data for topic B; a mismatch is treated as a malformed or suspicious request.","triggerScenarios":"Header topic 'foo' but body {\"topic\":\"bar\",...} in TopicQueueMappingDetail JSON. Happens when the header and the mapping detail are built from different variables (copy-paste in scripts, stale cached mapping detail reused across topics, or a loop variable bug in bulk static-topic creation).","commonSituations":"Bulk creation of static topics reusing a template TopicQueueMappingDetail without resetting its topic field; concurrent code mutating a shared mapping object; retry logic that pairs a new header with an old body.","solutions":["Set mappingDetail.setTopic(headerTopic) (or derive both from the same variable) before serializing the body.","Add an assertion StringUtils.equals(header.getTopic(), mappingDetail.getTopic()) before sending.","In bulk loops, construct a fresh TopicQueueMappingDetail per topic instead of mutating a shared instance."],"exampleFix":"// before\nCreateTopicRequestHeader h = newHeader(topicA);\nTopicQueueMappingDetail d = templateDetail; // still holds topicB\n\n// after\nCreateTopicRequestHeader h = newHeader(topicA);\nTopicQueueMappingDetail d = templateDeepCopy();\nd.setTopic(h.getTopic());","handlingStrategy":"validation","validationCode":"if (!StringUtils.equals(header.getTopic(), mappingDetail.getTopic())) {\n    throw new IllegalArgumentException(\"mapping topic must equal header topic\");\n}","typeGuard":null,"tryCatchPattern":"try { admin.createStaticTopic(header, mappingDetail); }\ncatch (AuthorizationException e) {\n    if (e.getMessage().contains(\"does not match\")) { mappingDetail.setTopic(header.getTopic()); resend(); return; }\n    throw e;\n}","preventionTips":["Derive header topic and mapping topic from one variable","Build a fresh TopicQueueMappingDetail per topic in bulk loops","Assert equality before serializing"],"tags":["rocketmq","authorization","static-topic","consistency-check"],"backgroundTag":null,"analyzedSha":"293f5885719fc4aa3619446a1900f58ccfcfdd29","analyzedAt":"2026-08-14T11:50:13.822Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}