{"id":"20fd0fd16c6eacee","repo":"apache/kafka","slug":"timeout-after-waiting-for-timeoutmillis-ms","errorCode":null,"errorMessage":"Timeout after waiting for {timeoutMillis} ms.","messagePattern":"Timeout after waiting for (.+?) ms\\.","errorType":"exception","errorClass":"TimeoutException","httpStatus":null,"severity":"error","filePath":"clients/src/main/java/org/apache/kafka/clients/producer/internals/FutureRecordMetadata.java","lineNumber":76,"sourceCode":"    }\n\n    @Override\n    public RecordMetadata get() throws InterruptedException, ExecutionException {\n        this.result.await();\n        if (nextRecordMetadata != null)\n            return nextRecordMetadata.get();\n        return valueOrError();\n    }\n\n    @Override\n    public RecordMetadata get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {\n        // Handle overflow.\n        long now = time.milliseconds();\n        long timeoutMillis = unit.toMillis(timeout);\n        long deadline = Long.MAX_VALUE - timeoutMillis < now ? Long.MAX_VALUE : now + timeoutMillis;\n        boolean occurred = this.result.await(timeout, unit);\n        if (!occurred)\n            throw new TimeoutException(\"Timeout after waiting for \" + timeoutMillis + \" ms.\");\n        if (nextRecordMetadata != null)\n            return nextRecordMetadata.get(deadline - time.milliseconds(), TimeUnit.MILLISECONDS);\n        return valueOrError();\n    }\n\n    /**\n     * This method is used when we have to split a large batch in smaller ones. A chained metadata will allow the\n     * future that has already returned to the users to wait on the newly created split batches even after the\n     * old big batch has been deemed as done.\n     */\n    void chain(FutureRecordMetadata futureRecordMetadata) {\n        if (nextRecordMetadata == null)\n            nextRecordMetadata = futureRecordMetadata;\n        else\n            nextRecordMetadata.chain(futureRecordMetadata);\n    }\n\n    RecordMetadata valueOrError() throws ExecutionException {","sourceCodeStart":58,"sourceCodeEnd":94,"githubUrl":"https://github.com/apache/kafka/blob/c31c9215e131f8c17e79f8901b48c13ee6aa8e7a/clients/src/main/java/org/apache/kafka/clients/producer/internals/FutureRecordMetadata.java#L58-L94","documentation":"Thrown by FutureRecordMetadata.get(long, TimeUnit) when the user-supplied wait elapses before the backing ProduceRequestResult completes. The future returned by KafkaProducer.send() wraps an in-flight batch; if the broker does not acknowledge that batch within the timeout the caller passes to get(), this TimeoutException is raised. It does NOT mean the record was lost — the batch may still be delivered later — only that the synchronous wait gave up. It surfaces user-side rather than from the producer's internal delivery timeout.","triggerScenarios":"Calling producer.send(record).get(N, TimeUnit.MILLISECONDS) (the bounded overload of Future.get) where N is shorter than the time needed for batching (linger.ms), retry backoff, or broker acknowledgement. Also hit when the application imposes a tight RPC deadline on each send while the broker is slow or partitions are under leader election.","commonSituations":"Tight per-request SLAs in request/reply services; brokers overloaded or in the middle of a leader election; linger.ms or retry.backoff.ms set higher than the get() timeout; network latency spikes; the record landed in a partition whose leader was momentarily unavailable and triggered a metadata refresh before sending.","solutions":["Increase the timeout passed to future.get(...) so it comfortably exceeds delivery.timeout.ms plus linger.ms and retry backoff.","Reduce linger.ms and retry.backoff.ms if lower latency is required and throughput can tolerate it.","Use the callback (Producer.send(record, Callback)) instead of get() so delivery is asynchronous and this bounded wait never happens.","Investigate broker-side latency (under-replicated partitions, slow disk, GC) if timeouts happen cluster-wide.","Verify delivery.timeout.ms >= linger.ms + request.timeout.ms + retry.backoff window; an inconsistent set pushes records past any reasonable get() timeout."],"exampleFix":"// before\nRecordMetadata md = producer.send(rec).get(500, TimeUnit.MILLISECONDS);\n\n// after\nproducer.send(rec, (metadata, e) -> {\n    if (e != null) { log.error(\"send failed\", e); return; }\n    log.info(\"acked offset {}\", metadata.offset());\n});","handlingStrategy":"try-catch","validationCode":"// Avoid blocking get() when not necessary; check done-ness first.\nif (future.isDone()) {\n    RecordMetadata md = future.get();\n} else {\n    // do not call future.get(timeout) unless you can act on a still-pending result\n}","typeGuard":null,"tryCatchPattern":"try {\n    RecordMetadata md = future.get(remaining, TimeUnit.MILLISECONDS);\n} catch (java.util.concurrent.TimeoutException te) {\n    // record is still in-flight, NOT failed; re-check later or rely on the Callback\n} catch (InterruptedException ie) {\n    Thread.currentThread().interrupt();\n} catch (java.util.concurrent.ExecutionException ee) {\n    // underlying produce failure is in ee.getCause()\n}","preventionTips":["Prefer producer.send(record, callback) over blocking future.get(timeout); the broker notifies you on completion.","Size get(timeout) against delivery.timeout.ms (send + retries + acks), not request.timeout.ms, to avoid spurious timeouts.","Treat TimeoutException as 'still pending', not 'lost'; the record may still be delivered after you give up waiting."],"tags":["producer","timeout","metadata","delivery","java"],"analyzedSha":"c31c9215e131f8c17e79f8901b48c13ee6aa8e7a","analyzedAt":"2026-08-03T12:34:05.770Z","schemaVersion":2}