{"id":"b5796a6357aed063","repo":"apache/kafka","slug":"attempt-to-retrieve-exception-from-future-which-ha","errorCode":null,"errorMessage":"Attempt to retrieve exception from future which hasn't failed","messagePattern":"Attempt to retrieve exception from future which hasn't failed","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"clients/src/main/java/org/apache/kafka/clients/consumer/internals/RequestFuture.java","lineNumber":111,"sourceCode":"\n    /**\n     * Check if the request is retriable. This is a convenience method for checking if\n     * the exception is an instance of {@link RetriableException}.\n     * @return true if it is retriable, false otherwise\n     * @throws IllegalStateException if the future is not complete or completed successfully\n     */\n    public boolean isRetriable() {\n        return exception() instanceof RetriableException;\n    }\n\n    /**\n     * Get the exception from a failed result (only available if the request failed)\n     * @return the exception set in {@link #raise(RuntimeException)}\n     * @throws IllegalStateException if the future is not complete or completed successfully\n     */\n    public RuntimeException exception() {\n        if (!failed())\n            throw new IllegalStateException(\"Attempt to retrieve exception from future which hasn't failed\");\n        return (RuntimeException) result.get();\n    }\n\n    /**\n     * Complete the request successfully. After this call, {@link #succeeded()} will return true\n     * and the value can be obtained through {@link #value()}.\n     * @param value corresponding value (or null if there is none)\n     * @throws IllegalStateException if the future has already been completed\n     * @throws IllegalArgumentException if the argument is an instance of {@link RuntimeException}\n     */\n    public void complete(T value) {\n        try {\n            if (value instanceof RuntimeException)\n                throw new IllegalArgumentException(\"The argument to complete can not be an instance of RuntimeException\");\n\n            if (!result.compareAndSet(INCOMPLETE_SENTINEL, value))\n                throw new IllegalStateException(\"Invalid attempt to complete a request future which is already complete\");\n            fireSuccess();","sourceCodeStart":93,"sourceCodeEnd":129,"githubUrl":"https://github.com/apache/kafka/blob/c31c9215e131f8c17e79f8901b48c13ee6aa8e7a/clients/src/main/java/org/apache/kafka/clients/consumer/internals/RequestFuture.java#L93-L129","documentation":"Thrown by RequestFuture.exception() when the caller asks for the failure cause of a future that has not actually failed (it is either still incomplete or has completed successfully). The future's internal result is checked with failed() (result instanceof RuntimeException); if that is false, the future has no exception to hand back. This is a programmer misuse of the future's state machine, not a network or broker condition.","triggerScenarios":"Calling future.exception() without first checking future.failed() (or future.succeeded()/isDone()); calling exception() on a future that was completed via complete(value); calling isRetriable() on a future that succeeded (isRetriable() delegates to exception()).","commonSituations":"Custom RequestFutureListener.onFailure implementations that unconditionally call exception(); adapter code in compose()/chain() that assumes failure; debugging code that logs future.exception() without a succeeded()/failed() guard; refactors that move exception() calls out of the else-branch of a succeeded() check.","solutions":["Guard every call to future.exception() (or future.isRetriable()) with if (future.failed()) { ... } else { ... }.","If you only need the success path, use future.value() inside an if (future.succeeded()) block and never touch exception() on that branch.","Audit custom RequestFutureListener and RequestFutureAdapter implementations, which are the usual sites that mishandle the success/failure split."],"exampleFix":"// before\nRequestFuture<ClientResponse> future = client.send(api, req);\nclient.poll(future);\nRuntimeException cause = future.exception(); // throws if succeeded\n\n// after\nRequestFuture<ClientResponse> future = client.send(api, req);\nclient.poll(future);\nif (future.failed()) {\n    throw future.exception();\n}\nClientResponse response = future.value();","handlingStrategy":"type-guard","validationCode":"// Always gate access to exception() with failed();\n// never call future.exception() unconditionally.\nif (future.isDone() && future.failed()) {\n    RuntimeException e = future.exception();\n    // handle e\n} else {\n    // future is incomplete or succeeded; do not ask for an exception\n}","typeGuard":"// Narrow a RequestFuture to its failed branch before reading exception.\n// Returns the exception only when the future has actually failed.\njava.util.function.Function<RequestFuture<?>, java.util.Optional<RuntimeException>> failedException =\n    f -> f.isDone() && f.failed()\n         ? java.util.Optional.of(f.exception())\n         : java.util.Optional.empty();","tryCatchPattern":"try {\n    RuntimeException e = future.exception();\n} catch (IllegalStateException ex) {\n    // future hadn't failed; fall back to value() or await completion\n    if (!future.isDone()) future.awaitDone(timeout, TimeUnit.MILLISECONDS);\n}","preventionTips":["Follow the javadoc contract: check succeeded()/failed() before calling value() or exception().","Treat RequestFuture as a state machine — never assume the terminal state is FAILURE.","Use the canonical pattern: poll(future) then branch on succeeded() before reading any result.","Note RequestFuture is an internal class (clients.consumer.internals); prefer the higher-level KafkaConsumer API which already follows this contract."],"tags":["consumer","async","future-state","misuse"],"analyzedSha":"c31c9215e131f8c17e79f8901b48c13ee6aa8e7a","analyzedAt":"2026-08-03T12:34:05.770Z","schemaVersion":2}