{"record":{"id":"3983a22194e28f4f","repo":"apache/rocketmq","slug":"dynamic-cause-message-from-recallmessagehandl","errorCode":null,"errorMessage":"{} (dynamic: cause message from RecallMessageHandle.decodeHandle failure)","messagePattern":"(.+?) \\(dynamic: cause message from RecallMessageHandle\\.decodeHandle failure\\)","errorType":"exception","errorClass":"MQClientException","httpStatus":null,"severity":"error","filePath":"client/src/main/java/org/apache/rocketmq/client/impl/producer/DefaultMQProducerImpl.java","lineNumber":1582,"sourceCode":"        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        }\n        RecallMessageRequestHeader requestHeader = new RecallMessageRequestHeader();\n        requestHeader.setProducerGroup(this.defaultMQProducer.getProducerGroup());\n        requestHeader.setTopic(topic);\n        requestHeader.setRecallHandle(recallHandle);\n        requestHeader.setBrokerName(handleEntity.getBrokerName());\n        return this.mQClientFactory.getMQClientAPIImpl().recallMessage(brokerAddr,","sourceCodeStart":1564,"sourceCodeEnd":1600,"githubUrl":"https://github.com/apache/rocketmq/blob/293f5885719fc4aa3619446a1900f58ccfcfdd29/client/src/main/java/org/apache/rocketmq/client/impl/producer/DefaultMQProducerImpl.java#L1564-L1600","documentation":"Dynamic MQClientException whose message and null-cause come from an exception raised while RecallMessageHandle.decodeHandle(recallHandle) parsed the handle (the '{}' placeholder is the parse error text; the annotated suffix marks it as dynamic). The recall handle is a broker-issued opaque token that must be base64/struct-decodable into HandleV1; anything undecodable fails here before any network call.","triggerScenarios":"Passing a malformed, truncated, or tampered recallHandle string; passing a handle from an incompatible RocketMQ version whose HandleV1 encoding differs; URL-decoding/escaping corruption (e.g. '+' vs space) when the handle transited a query parameter or log pipeline.","commonSituations":"Copying handles out of logs/consoles with whitespace or quoting damage; handles forwarded through HTTP APIs where encoding is altered; version skew between the broker that issued the handle and the client decoding it.","solutions":["Re-copy the handle exactly as delivered by the broker/send result, without trimming, decoding, or re-encoding.","If the handle traveled through a URL or JSON layer, ensure lossless round-trip (URL-encode as one token, treat as opaque string).","Verify the client version matches the broker that produced the handle so HandleV1 layout agrees.","Add a pre-check that the handle is non-empty and plausibly base64 before calling recallMessage, surfacing a clear validation error to users."],"exampleFix":"// before\nString handle = params.get(\"handle\").replace(\" \", \"+\"); // corrupted transit\nproducer.recallMessage(topic, handle);\n\n// after\nString handle = params.get(\"handle\"); // transported URL-encoded, decoded exactly once by the framework\nif (handle == null || handle.isBlank()) throw new IllegalArgumentException(\"recall handle required\");\nproducer.recallMessage(topic, handle);","handlingStrategy":"validation","validationCode":"// handle must be non-blank and plausibly base64 before calling recallMessage\nif (recallHandle == null || recallHandle.isBlank()\n        || !recallHandle.matches(\"[A-Za-z0-9+/=_-]+\")) {\n    throw new IllegalArgumentException(\"malformed recall handle\");\n}","typeGuard":"static boolean isPlausibleRecallHandle(String h) {\n    return h != null && !h.isBlank() && h.matches(\"[A-Za-z0-9+/=_-]+\");\n}","tryCatchPattern":"try {\n    producer.recallMessage(topic, recallHandle);\n} catch (MQClientException e) {\n    // decode failures surface with the parser's message and null cause\n    log.warn(\"recall handle rejected for topic {}: {}\", topic, e.getMessage());\n    reRequestHandleFromSource(); // ask the issuing system for a fresh handle\n}","preventionTips":["Treat recall handles as opaque: never trim, decode, or re-encode them in transit.","Use URL-encoding when handles cross HTTP boundaries to survive '+' and '=' mangling.","Keep client and broker versions in sync so HandleV1 wire format matches."],"tags":["rocketmq","producer","message-recall","handle-decode","malformed-input"],"backgroundTag":null,"analyzedSha":"293f5885719fc4aa3619446a1900f58ccfcfdd29","analyzedAt":"2026-08-14T11:50:13.822Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}