apache/rocketmq · error · RequestTimeoutException
REQUEST_TIMEOUT_EXCEPTION
REQUEST_TIMEOUT_EXCEPTION
Error message
send request message to <{}> OK, but wait reply message timeout, {} ms. What it means
RequestTimeoutException (code REQUEST_TIMEOUT_EXCEPTION) thrown by waitResponse in the request() RPC-over-MQ flow when the reply future times out and returns null, but the request message itself was sent successfully (isSendRequestOk()). So the requester's message reached the broker, yet no correlated reply arrived within the remaining timeout budget (timeout - cost of sending).
Source
Thrown at client/src/main/java/org/apache/rocketmq/client/impl/producer/DefaultMQProducerImpl.java:1774
public void onException(Throwable e) {
requestResponseFuture.setSendRequestOk(false);
requestResponseFuture.putResponseMessage(null);
requestResponseFuture.setCause(e);
}
}, null, timeout - cost);
return waitResponse(msg, timeout, requestResponseFuture, cost);
} finally {
RequestFutureHolder.getInstance().getRequestFutureTable().remove(correlationId);
}
}
private Message waitResponse(Message msg, long timeout, RequestResponseFuture requestResponseFuture,
long cost) throws InterruptedException, RequestTimeoutException, MQClientException {
Message responseMessage = requestResponseFuture.waitResponseMessage(timeout - cost);
if (responseMessage == null) {
if (requestResponseFuture.isSendRequestOk()) {
throw new RequestTimeoutException(ClientErrorCode.REQUEST_TIMEOUT_EXCEPTION,
"send request message to <" + msg.getTopic() + "> OK, but wait reply message timeout, " + timeout + " ms.");
} else {
throw new MQClientException("send request message to <" + msg.getTopic() + "> fail", requestResponseFuture.getCause());
}
}
return responseMessage;
}
public void request(final Message msg, final MessageQueue mq, final RequestCallback requestCallback, long timeout)
throws RemotingException, InterruptedException, MQClientException, MQBrokerException {
long beginTimestamp = System.currentTimeMillis();
prepareSendRequest(msg, timeout);
final String correlationId = msg.getProperty(MessageConst.PROPERTY_CORRELATION_ID);
final RequestResponseFuture requestResponseFuture = new RequestResponseFuture(correlationId, timeout, requestCallback);
RequestFutureHolder.getInstance().getRequestFutureTable().put(correlationId, requestResponseFuture);
long cost = System.currentTimeMillis() - beginTimestamp;View on GitHub (pinned to 293f588571)
Solutions
- Measure the responder's p99 processing time and set the request timeout comfortably above it (send time + processing + reply delivery).
- Verify the responder side actually replies (check its logs/consumer stats and that it sends to the correlation reply topic).
- Make the responder asynchronous if it cannot answer within the budget, or switch to a fire-and-forget + callback pattern (request with RequestCallback).
- For timeout-but-sent cases, treat as ambiguous: the responder may still have processed the request, so make the operation idempotent before retrying.
Example fix
// before
Message reply = producer.request(msg, 1000L); // responder p99 is 2s
// after
Message reply;
try {
reply = producer.request(msg, 5000L);
} catch (RequestTimeoutException e) {
// request was delivered; retry only with an idempotency key
if (!idempotency.containsKey(msgKey)) retryQueue.add(msg);
throw e;
} Defensive patterns
Strategy: retry
Validate before calling
// bound timeout by responder's measured p99 before issuing request
long budget = sendRttMs + responderP99Ms + slackMs;
if (configuredTimeout < budget) {
throw new IllegalStateException("request timeout " + configuredTimeout
+ "ms below required budget " + budget + "ms");
} Try / catch
try {
Message reply = producer.request(msg, timeout);
} catch (RequestTimeoutException e) {
// request WAS delivered — only retry if operation is idempotent
if (idempotencyGuard.tryAcquire(correlationId)) {
producer.request(msg, longerTimeout);
} else {
log.warn("reply timeout for delivered request {}", correlationId);
}
} Prevention
- Track responder latency percentiles and set request timeouts above send+processing+reply.
- Make request-handling idempotent because timeouts leave delivery/processing ambiguous.
- Monitor reply-topic consumption on the responder side; silent non-reply is the top cause.
When it happens
Trigger: producer.request(msg, timeout) where the responding service consumed the request but did not reply in time: responder down/slow, reply topic ('%REPLY%' flow) not routed back, correlation-id mismatch, or timeout too small relative to responder latency.
Common situations: RPC pattern over RocketMQ with a responder that does DB work slower than the configured timeout; responder instances not subscribed to the request topic; reply messages dropped due to permissions; client-side RequestFutureHolder cleaned up on shutdown before replies arrive.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- send request message to <{}> fail
- sendMessage call timeout
- sendSelectImpl call timeout
- sendDefaultImpl call timeout
- sendKernelImpl call timeout
AI-assisted analysis of apache/rocketmq@293f588571 (2026-08-14).
Data as JSON: /api/errors/90f0ce0640db5440.
Report an issue: GitHub.