{"record":{"id":"216cde35073c980b","repo":"apache/rocketmq","slug":"transactional-messages-do-not-support-delayed-deli","errorCode":null,"errorMessage":"Transactional messages do not support delayed delivery","messagePattern":"Transactional messages do not support delayed delivery","errorType":"validation","errorClass":"MQClientException","httpStatus":null,"severity":"error","filePath":"client/src/main/java/org/apache/rocketmq/client/impl/producer/DefaultMQProducerImpl.java","lineNumber":1516,"sourceCode":"            log.warn(\"local transaction execute {}, but end broker transaction failed\", localTransactionState, e);\n        }\n\n        TransactionSendResult transactionSendResult = new TransactionSendResult();\n        transactionSendResult.setSendStatus(sendResult.getSendStatus());\n        transactionSendResult.setMessageQueue(sendResult.getMessageQueue());\n        transactionSendResult.setMsgId(sendResult.getMsgId());\n        transactionSendResult.setQueueOffset(sendResult.getQueueOffset());\n        transactionSendResult.setTransactionId(sendResult.getTransactionId());\n        transactionSendResult.setLocalTransactionState(localTransactionState);\n        return transactionSendResult;\n    }\n\n    private void ensureNotDelayedForTransactional(final Message msg) throws MQClientException {\n        if (msg.getProperty(MessageConst.PROPERTY_DELAY_TIME_LEVEL) != null\n                || msg.getProperty(MessageConst.PROPERTY_TIMER_DELAY_MS) != null\n                || msg.getProperty(MessageConst.PROPERTY_TIMER_DELAY_SEC) != null\n                || msg.getProperty(MessageConst.PROPERTY_TIMER_DELIVER_MS) != null) {\n            throw new MQClientException(\"Transactional messages do not support delayed delivery\", null);\n        }\n    }\n\n    /**\n     * DEFAULT SYNC -------------------------------------------------------\n     */\n    public SendResult send(\n        Message msg) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {\n        return send(msg, this.defaultMQProducer.getSendMsgTimeout());\n    }\n\n    public void endTransaction(\n        final Message msg,\n        final SendResult sendResult,\n        final LocalTransactionState localTransactionState,\n        final Throwable localException) throws RemotingException, MQBrokerException, InterruptedException, UnknownHostException {\n        final MessageId id;\n        if (sendResult.getOffsetMsgId() != null) {","sourceCodeStart":1498,"sourceCodeEnd":1534,"githubUrl":"https://github.com/apache/rocketmq/blob/293f5885719fc4aa3619446a1900f58ccfcfdd29/client/src/main/java/org/apache/rocketmq/client/impl/producer/DefaultMQProducerImpl.java#L1498-L1534","documentation":"Thrown by ensureNotDelayedForTransactional when a message submitted to sendMessageInTransaction carries any delay property: PROPERTY_DELAY_TIME_LEVEL, PROPERTY_TIMER_DELAY_MS, PROPERTY_TIMER_DELAY_SEC, or PROPERTY_TIMER_DELIVER_MS. RocketMQ's transactional protocol requires the half message to be consumable/committable immediately, which conflicts with delayed/timer-based delivery, so the client rejects the combination up front.","triggerScenarios":"Setting msg.setDelayTimeLevel(n) (or the timer delay properties via setDelayTimeMs/setDeliverTimeMs/user properties) and then passing that message to sendMessageInTransaction.","commonSituations":"A shared message-builder utility that always applies a delay, reused for transactional sends; migrating legacy delay-level code onto the request-reply/transaction API; setting '__TIMER_DELIVER_MS' as a user property manually.","solutions":["Remove delay settings before the transactional send: rebuild the message without setDelayTimeLevel/setDelayTimeMs/setDeliverTimeMs.","Split the workflow: send the transactional message first, and after commit send a separate delayed follow-up message if a timed action is needed.","Audit shared message-construction helpers for unconditional delay properties.","If a 'commit after delay' pattern is required, implement it with a timer topic or scheduler service, not by combining the two features."],"exampleFix":"// before\nMessage msg = new Message(topic, body);\nmsg.setDelayTimeLevel(3);\nproducer.sendMessageInTransaction(msg, listener, arg);\n\n// after\nMessage msg = new Message(topic, body);\nTransactionSendResult r = producer.sendMessageInTransaction(msg, listener, arg);\nif (r.getLocalTransactionState() == LocalTransactionState.COMMIT_MESSAGE) {\n    Message delayed = new Message(topic, body);\n    delayed.setDelayTimeLevel(3);\n    producer.send(delayed); // delayed follow-up, separate from the tx\n}","handlingStrategy":"validation","validationCode":"static void assertSendableAsTransactional(Message msg) {\n    for (String p : new String[]{\"DELAY\", MessageConst.PROPERTY_TIMER_DELAY_MS,\n            MessageConst.PROPERTY_TIMER_DELAY_SEC, MessageConst.PROPERTY_TIMER_DELIVER_MS}) {\n        if (msg.getProperty(p) != null)\n            throw new IllegalArgumentException(\"delay property \" + p + \" not allowed on transactional message\");\n    }\n}","typeGuard":null,"tryCatchPattern":"try {\n    producer.sendMessageInTransaction(msg, listener, arg);\n} catch (MQClientException e) {\n    if (e.getMessage().contains(\"do not support delayed delivery\")) {\n        Message plain = cloneWithoutDelay(msg);\n        producer.sendMessageInTransaction(plain, listener, arg);\n    } else throw e;\n}","preventionTips":["Never route messages built by a delay-applying builder into transactional sends.","Centralize transactional message construction in one factory that forbids delay setters.","Implement 'delayed commit' workflows as a delayed follow-up message after transaction success."],"tags":["rocketmq","producer","transactional-message","delayed-message","validation"],"backgroundTag":null,"analyzedSha":"293f5885719fc4aa3619446a1900f58ccfcfdd29","analyzedAt":"2026-08-14T11:50:13.822Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}