{"record":{"id":"90f0ce0640db5440","repo":"apache/rocketmq","slug":"request-timeout-exception","errorCode":"REQUEST_TIMEOUT_EXCEPTION","errorMessage":"send request message to <{}> OK, but wait reply message timeout, {} ms.","messagePattern":"send request message to <(.+?)> OK, but wait reply message timeout, (.+?) ms\\.","errorType":"exception","errorClass":"RequestTimeoutException","httpStatus":null,"severity":"error","filePath":"client/src/main/java/org/apache/rocketmq/client/impl/producer/DefaultMQProducerImpl.java","lineNumber":1774,"sourceCode":"                public void onException(Throwable e) {\n                    requestResponseFuture.setSendRequestOk(false);\n                    requestResponseFuture.putResponseMessage(null);\n                    requestResponseFuture.setCause(e);\n                }\n            }, null, timeout - cost);\n\n            return waitResponse(msg, timeout, requestResponseFuture, cost);\n        } finally {\n            RequestFutureHolder.getInstance().getRequestFutureTable().remove(correlationId);\n        }\n    }\n\n    private Message waitResponse(Message msg, long timeout, RequestResponseFuture requestResponseFuture,\n        long cost) throws InterruptedException, RequestTimeoutException, MQClientException {\n        Message responseMessage = requestResponseFuture.waitResponseMessage(timeout - cost);\n        if (responseMessage == null) {\n            if (requestResponseFuture.isSendRequestOk()) {\n                throw new RequestTimeoutException(ClientErrorCode.REQUEST_TIMEOUT_EXCEPTION,\n                    \"send request message to <\" + msg.getTopic() + \"> OK, but wait reply message timeout, \" + timeout + \" ms.\");\n            } else {\n                throw new MQClientException(\"send request message to <\" + msg.getTopic() + \"> fail\", requestResponseFuture.getCause());\n            }\n        }\n        return responseMessage;\n    }\n\n    public void request(final Message msg, final MessageQueue mq, final RequestCallback requestCallback, long timeout)\n        throws RemotingException, InterruptedException, MQClientException, MQBrokerException {\n        long beginTimestamp = System.currentTimeMillis();\n        prepareSendRequest(msg, timeout);\n        final String correlationId = msg.getProperty(MessageConst.PROPERTY_CORRELATION_ID);\n\n        final RequestResponseFuture requestResponseFuture = new RequestResponseFuture(correlationId, timeout, requestCallback);\n        RequestFutureHolder.getInstance().getRequestFutureTable().put(correlationId, requestResponseFuture);\n\n        long cost = System.currentTimeMillis() - beginTimestamp;","sourceCodeStart":1756,"sourceCodeEnd":1792,"githubUrl":"https://github.com/apache/rocketmq/blob/293f5885719fc4aa3619446a1900f58ccfcfdd29/client/src/main/java/org/apache/rocketmq/client/impl/producer/DefaultMQProducerImpl.java#L1756-L1792","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nMessage reply = producer.request(msg, 1000L); // responder p99 is 2s\n\n// after\nMessage reply;\ntry {\n    reply = producer.request(msg, 5000L);\n} catch (RequestTimeoutException e) {\n    // request was delivered; retry only with an idempotency key\n    if (!idempotency.containsKey(msgKey)) retryQueue.add(msg);\n    throw e;\n}","handlingStrategy":"retry","validationCode":"// bound timeout by responder's measured p99 before issuing request\nlong budget = sendRttMs + responderP99Ms + slackMs;\nif (configuredTimeout < budget) {\n    throw new IllegalStateException(\"request timeout \" + configuredTimeout\n        + \"ms below required budget \" + budget + \"ms\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    Message reply = producer.request(msg, timeout);\n} catch (RequestTimeoutException e) {\n    // request WAS delivered — only retry if operation is idempotent\n    if (idempotencyGuard.tryAcquire(correlationId)) {\n        producer.request(msg, longerTimeout);\n    } else {\n        log.warn(\"reply timeout for delivered request {}\", correlationId);\n    }\n}","preventionTips":["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."],"tags":["rocketmq","producer","request-reply","timeout","rpc"],"backgroundTag":null,"analyzedSha":"293f5885719fc4aa3619446a1900f58ccfcfdd29","analyzedAt":"2026-08-14T11:50:13.822Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}