#11037·rocketmq

[Bug] Delay messages are delivered with internal properties leaked into propertiesString (ScheduleMessageService#messageTimeUp)

Author: unbridled-41Created Sep 5, 2026Updated Sep 17, 2026

Before Creating the Bug Report

  • I found a bug, not just asking a question, which should be created in GitHub Discussions.
  • I have searched the GitHub Issues and GitHub Discussions of this repository and believe that this is not a duplicate.
  • I have confirmed that this bug belongs to the current repository, not other repositories of RocketMQ.

Runtime platform environment

Linux, JDK 21, develop (ff8f6f74c)

RocketMQ version

5.x develop

Describe the Bug

ScheduleMessageService#messageTimeUp (broker/src/main/java/org/apache/rocketmq/broker/schedule/ScheduleMessageService.java) encodes the delivered message's propertiesString before the internal properties are cleared:

java
msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgExt.getProperties())); // line 344 — encode
...
MessageAccessor.clearProperty(msgInner, MessageConst.PROPERTY_DELAY_TIME_LEVEL);   // line 353 — clear after
MessageAccessor.clearProperty(msgInner, MessageConst.PROPERTY_TIMER_DELIVER_MS);
MessageAccessor.clearProperty(msgInner, MessageConst.PROPERTY_TIMER_DELAY_SEC);

The propertiesString is what actually gets persisted in the commitlog and decoded on the consumer side, so every delay-level message delivered by ScheduleMessageService still carries DELAY_TIME_LEVEL (and TIMER_DELIVER_MS/TIMER_DELAY_SEC when the producer used timer properties) on the wire, while the broker-side property map no longer has them. The property map and the wire data disagree; SQL92 property filtering and user code see stale internal properties.

This is the exact defect class fixed for the timer-wheel path in #10972 / commit e533b663f (TimerMessageStore#convertMessage now encodes after clearing). The same pattern in ScheduleMessageService#messageTimeUp predates it (old unmerged PR #4190 tried to address it in 2022) and was not covered by that fix.

Steps to Reproduce

  1. Send a message with setDelayTimeLevel(n).
  2. When the delay expires, messageTimeUp re-writes it into the real topic.
  3. Consume the message: message.getProperties() / getProperty("DELAY_TIME_LEVEL") (decoded from propertiesString) still returns the internal delay properties.

Expected Behavior

The delivered message's propertiesString must be encoded after the internal properties are cleared, so the property map and the wire data are consistent and no internal properties leak to consumers.

Corresponding PR

  • Fix PR: #11038 (linked with Closes #11037 in the PR description; contains the regression test that fails before the fix and passes after it).