Netflix/Hystrix · error · IllegalStateException

Response has already terminated so response can not be set :

Error message

Response has already terminated so response can not be set : {response}

What it means

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.

Source

Thrown at hystrix-core/src/main/java/com/netflix/hystrix/collapser/CollapsedRequestSubject.java:107

    public R getArgument() {
        return argument;
    }

    /**
     * When set any client thread blocking on get() will immediately be unblocked and receive the single-valued response.
     * 
     * @throws IllegalStateException
     *             if called more than once or after setException.
     * @param response response to give to initial command
     */
    @Override
    public void setResponse(T response) {
        if (!isTerminated()) {
            subject.onNext(response);
            valueSet.set(true);
            subject.onCompleted();
        } else {
            throw new IllegalStateException("Response has already terminated so response can not be set : " + response);
        }
    }

    /**
     * Emit a response that should be OnNexted to an Observer
     * @param response response to emit to initial command
     */
    @Override
    public void emitResponse(T response) {
        if (!isTerminated()) {
            subject.onNext(response);
            valueSet.set(true);
        } else {
            throw new IllegalStateException("Response has already terminated so response can not be set : " + response);
        }
    }

    @Override

View on GitHub (pinned to 5ce3bc58c3)

Solutions

  1. Audit mapResponseToRequests: each request must receive exactly ONE of setResponse/emitResponse-final/setException/setComplete
  2. Guard each call: if (request.isTerminated()) skip / or use setException-if-not-set semantics where appropriate
  3. Ensure error paths return from the mapping method instead of falling through to response-setting code
  4. Add unit tests with partially-failing batches (one request errors, others succeed)

Example fix

// before
for (CollapsedRequest<String, Integer> r : requests) {
  try { r.setResponse(map.get(r.getArgument())); }
  catch (Exception e) { r.setException(e); }
  if (someFlag) r.setResponse(fallbackValue); // second terminal -> ISE
}
// after
for (CollapsedRequest<String, Integer> r : requests) {
  try { r.setResponse(map.get(r.getArgument())); }
  catch (Exception e) { r.setException(e); }
}
Defensive patterns

Strategy: validation

Validate before calling

// before mapping, check termination state via a tracked set of served requests
Set<CollapsedRequest<R, A>> served = new HashSet<>();
// in loop: if (!served.contains(request)) { request.setResponse(v); served.add(request); }

Type guard

null

Try / catch

catch (IllegalStateException e) { if (e.getMessage().contains("already terminated")) { // mapping bug: log request argument and skip — do not kill the whole batch } }

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Related errors


AI-assisted analysis of Netflix/Hystrix@5ce3bc58c3 (2026-08-14). Data as JSON: /api/errors/bf0dfa7a0bf1d703. Report an issue: GitHub.