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

  1. Measure the responder's p99 processing time and set the request timeout comfortably above it (send time + processing + reply delivery).
  2. Verify the responder side actually replies (check its logs/consumer stats and that it sends to the correlation reply topic).
  3. Make the responder asynchronous if it cannot answer within the budget, or switch to a fire-and-forget + callback pattern (request with RequestCallback).
  4. 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

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

Related errors


AI-assisted analysis of apache/rocketmq@293f588571 (2026-08-14). Data as JSON: /api/errors/90f0ce0640db5440. Report an issue: GitHub.