{"record":{"id":"bf0dfa7a0bf1d703","repo":"Netflix/Hystrix","slug":"response-has-already-terminated-so-response-can-no","errorCode":null,"errorMessage":"Response has already terminated so response can not be set : {response}","messagePattern":"Response has already terminated so response can not be set : (.+?)","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"hystrix-core/src/main/java/com/netflix/hystrix/collapser/CollapsedRequestSubject.java","lineNumber":107,"sourceCode":"    public R getArgument() {\n        return argument;\n    }\n\n    /**\n     * When set any client thread blocking on get() will immediately be unblocked and receive the single-valued response.\n     * \n     * @throws IllegalStateException\n     *             if called more than once or after setException.\n     * @param response response to give to initial command\n     */\n    @Override\n    public void setResponse(T response) {\n        if (!isTerminated()) {\n            subject.onNext(response);\n            valueSet.set(true);\n            subject.onCompleted();\n        } else {\n            throw new IllegalStateException(\"Response has already terminated so response can not be set : \" + response);\n        }\n    }\n\n    /**\n     * Emit a response that should be OnNexted to an Observer\n     * @param response response to emit to initial command\n     */\n    @Override\n    public void emitResponse(T response) {\n        if (!isTerminated()) {\n            subject.onNext(response);\n            valueSet.set(true);\n        } else {\n            throw new IllegalStateException(\"Response has already terminated so response can not be set : \" + response);\n        }\n    }\n\n    @Override","sourceCodeStart":89,"sourceCodeEnd":125,"githubUrl":"https://github.com/Netflix/Hystrix/blob/5ce3bc58c38e7ca60ef2fe0e516e390e294ad941/hystrix-core/src/main/java/com/netflix/hystrix/collapser/CollapsedRequestSubject.java#L89-L125","documentation":"CollapsedRequestSubject.setResponse() pushes a batch result to a single collapsed request; it is legal only while the underlying subject is not terminated (hasCompleted()/hasThrowable() both false). Calling it after the subject already completed or errored throws IllegalStateException('Response has already terminated so response can not be set'). This almost always indicates a bug in the collapser's mapResponseToRequests.","triggerScenarios":"A mapResponseToRequests implementation that calls setResponse()/setException() twice for one request, or calls setResponse after setComplete/exception already terminated that request (e.g. error path then success path both execute in a loop).","commonSituations":"Hand-written batch mapping logic with early-return error handling that later continues the loop and sets responses again; shard-parallel batch responses racing to complete the same CollapsedRequest; refactoring a collapser and losing the 'one terminal event per request' invariant.","solutions":["Audit mapResponseToRequests: each request must receive exactly ONE of setResponse/emitResponse-final/setException/setComplete","Guard each call: if (request.isTerminated()) skip / or use setException-if-not-set semantics where appropriate","Ensure error paths return from the mapping method instead of falling through to response-setting code","Add unit tests with partially-failing batches (one request errors, others succeed)"],"exampleFix":"// before\nfor (CollapsedRequest<String, Integer> r : requests) {\n  try { r.setResponse(map.get(r.getArgument())); }\n  catch (Exception e) { r.setException(e); }\n  if (someFlag) r.setResponse(fallbackValue); // second terminal -> ISE\n}\n// after\nfor (CollapsedRequest<String, Integer> r : requests) {\n  try { r.setResponse(map.get(r.getArgument())); }\n  catch (Exception e) { r.setException(e); }\n}","handlingStrategy":"validation","validationCode":"// before mapping, check termination state via a tracked set of served requests\nSet<CollapsedRequest<R, A>> served = new HashSet<>();\n// in loop: if (!served.contains(request)) { request.setResponse(v); served.add(request); }","typeGuard":"null","tryCatchPattern":"catch (IllegalStateException e) { if (e.getMessage().contains(\"already terminated\")) { // mapping bug: log request argument and skip — do not kill the whole batch } }","preventionTips":["Enforce one terminal event per CollapsedRequest in mapResponseToRequests","Return early from error branches instead of falling through","Test with mixed success/failure batches"],"tags":["java","hystrix","collapser","rxjava","lifecycle"],"backgroundTag":null,"analyzedSha":"5ce3bc58c38e7ca60ef2fe0e516e390e294ad941","analyzedAt":"2026-08-14T10:55:35.600Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}