When a consumer fails to process a message or the processing times out, ApsaraMQ for RocketMQ automatically redelivers the message based on a retry policy. After all retry attempts are exhausted, the message is moved to a dead-letter queue. You can consume messages in dead-letter queues to restore your business.
Usage notes
The message ID remains unchanged across all retry attempts.
Consumption retry works only in clustering consumption mode. In broadcasting consumption mode, failed messages are not redelivered -- the consumer skips to the next message.
How it works
A message transitions through the following states:
| State | Description |
|---|---|
| Ready | The message is queued in the broker and available for consumption. |
| Inflight | A consumer has pulled the message and is processing it. No result has been returned yet. |
| WaitingRetry | Consumption failed or timed out. The message waits for the retry interval to elapse before returning to the Ready state. |
| Commit | The consumer returned a success response. Consumption is complete. |
| DLQ | If the feature for retaining dead-letter messages is enabled, the message is moved to the dead-letter topic after exhausting all retry attempts. |

Retry timing
The interval between two consecutive consumption attempts has three components:
Interval between attempts = consumption duration + retry interval + time in Ready state

For example, assume a message waits 5 seconds in the Ready state and takes 6 seconds to process before failing. With a 10-second retry interval:
0 s -- The message enters the Ready state.
5 s -- The consumer pulls the message (Inflight).
11 s -- Consumption fails after 6 seconds. The message enters WaitingRetry.
21 s -- After the 10-second retry interval, the message returns to Ready.
26 s -- The consumer pulls the message again for the next attempt.
Total interval: 6 + 10 + 5 = 21 seconds.
Retry policies for TCP
Ordered messages
| Setting | Details |
|---|---|
| Retry interval | Set with the suspendTimeMillis parameter. Valid range: 10 -- 30,000 ms. Default: 1,000 ms (1 second). |
| Maximum retries | Set with the MaxReconsumeTimes parameter. No upper limit. Default: Integer.MAX. |
Unordered messages
| Setting | Details |
|---|---|
| Retry interval | Follows a predefined escalating schedule (see table below). Custom intervals are not supported for unordered messages. |
| Maximum retries | Set with the MaxReconsumeTimes parameter. No upper limit. Default: 16. |
If MaxReconsumeTimes exceeds 16, the predefined schedule applies for the first 16 retries. All subsequent retries use a fixed 2-hour interval.
Retry interval schedule for unordered messages
| Retry | Interval | Retry | Interval |
|---|---|---|---|
| 1 | 10 seconds | 9 | 7 minutes |
| 2 | 30 seconds | 10 | 8 minutes |
| 3 | 1 minute | 11 | 9 minutes |
| 4 | 2 minutes | 12 | 10 minutes |
| 5 | 3 minutes | 13 | 20 minutes |
| 6 | 4 minutes | 14 | 30 minutes |
| 7 | 5 minutes | 15 | 1 hour |
| 8 | 6 minutes | 16 | 2 hours |
Retry policies for HTTP
HTTP retry policies are predefined and cannot be modified.
| Message type | Retry interval | Maximum retries |
|---|---|---|
| Ordered | 1 minute | 288 |
| Unordered | 5 minutes | 288 |
Configure retry behavior for TCP
Enable retry
To trigger a retry when consumption fails in clustering consumption mode, implement the MessageListener interface with one of these approaches:
Return
Action.ReconsumeLater(recommended)Return
nullThrow an exception
public class MessageListenerImpl implements MessageListener {
@Override
public Action consume(Message message, ConsumeContext context) {
// If the consumption logic throws an exception, the message is retried.
doConsumeMessage(message);
// Method 1: Return Action.ReconsumeLater and retry the message.
return Action.ReconsumeLater;
// Method 2: Return null and retry the message.
return null;
// Method 3: Throw an exception and retry the message.
throw new RuntimeException("Consumer Message exception");
}
}Disable retry
To prevent redelivery, catch all exceptions in your consumption logic and return Action.CommitMessage:
public class MessageListenerImpl implements MessageListener {
@Override
public Action consume(Message message, ConsumeContext context) {
try {
doConsumeMessage(message);
} catch (Throwable e) {
// Catch all exceptions and return CommitMessage to skip retry.
return Action.CommitMessage;
}
// Consumption succeeded.
return Action.CommitMessage;
}
}Customize the retry interval and maximum retries
Custom retry configuration requires TCP client SDK for Java version 1.2.2 or later. For more information, see Release notes.
Set MaxReconsumeTimes and SuspendTimeMillis in the consumer properties before starting the consumer. Custom retry intervals apply only to ordered messages. Unordered messages always follow the predefined schedule.
Properties properties = new Properties();
// Set the maximum number of retries to 20.
properties.put(PropertyKeyConst.MaxReconsumeTimes, "20");
// Set the retry interval to 3,000 milliseconds (ordered messages only).
properties.put(PropertyKeyConst.SuspendTimeMillis, "3000");
Consumer consumer = ONSFactory.createConsumer(properties);All consumers in the same consumer group share the retry configuration. The most recently started consumer overwrites the configuration of earlier consumers. Make sure all consumers in a group use identical MaxReconsumeTimes and SuspendTimeMillis values.
Query the retry count
After a consumer receives a message, call message.getReconsumeTimes() to check how many times the message has been retried:
public class MessageListenerImpl implements MessageListener {
@Override
public Action consume(Message message, ConsumeContext context) {
// Get the current retry count.
System.out.println(message.getReconsumeTimes());
return Action.CommitMessage;
}
}Best practices
Start with default retry values. The default schedule for unordered messages (escalating from 10 seconds to 2 hours over 16 retries) works well for most transient failures. Adjust only after observing your system's actual failure patterns.
Set up dead-letter queue monitoring. Messages that exhaust all retries are moved to a dead-letter queue. Monitor this queue and set up alerts so you can investigate and reprocess failed messages promptly.
Keep consumption logic idempotent. Because messages may be delivered more than once during retries, design your consumer to handle duplicate processing safely.
Avoid long-running consumption. If processing takes too long, the message may time out and trigger an unnecessary retry. Break long tasks into smaller steps or increase the consumption timeout.
What's next
Dead-letter queues -- Handle messages that fail after all retry attempts.
Release notes -- Check TCP client SDK version requirements.