Netflix/Hystrix · error · IllegalStateException

Response has already terminated so exception can not be set

Error message

Response has already terminated so exception can not be set

What it means

CollapsedRequestSubject.setException() delivers a failure to a single collapsed request and is legal only pre-termination; after the request was completed or already errored it throws IllegalStateException('Response has already terminated so exception can not be set'). The original exception is attached as the cause. Typically a double-termination bug in mapResponseToRequests.

Source

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

            setExceptionIfResponseNotReceived(exception);
        }
        // return any exception that was generated
        return exception;
    }

    /**
     * When set any client thread blocking on get() will immediately be unblocked and receive the exception.
     * 
     * @throws IllegalStateException
     *             if called more than once or after setResponse.
     * @param e received exception that gets set on the initial command
     */
    @Override
    public void setException(Exception e) {
        if (!isTerminated()) {
            subject.onError(e);
        } else {
            throw new IllegalStateException("Response has already terminated so exception can not be set", e);
        }
    }

    private boolean isTerminated() {
        return (subject.hasCompleted() || subject.hasThrowable());
    }

    public Observable<T> toObservable() {
        return subjectWithAccounting;
    }
}

View on GitHub (pinned to 5ce3bc58c3)

Solutions

  1. Set the exception only on requests that have not yet been terminated; filter with a completed-set or check subject state via toObservable() materialization
  2. On batch failure, set exceptions OR responses, never both for the same request
  3. Write tests covering partial batch failure and duplicate mapping

Example fix

// before
try { batchResponse.forEach(r -> request.setResponse(r)); }
catch (Exception e) { requests.forEach(q -> q.setException(e)); } // some already set -> ISE
// after
Set<CollapsedRequest<?, ?>> done = ...;
try { batchResponse.forEach(r -> { request.setResponse(r); done.add(request); }); }
catch (Exception e) { for (q : requests) if (!done.contains(q)) q.setException(e); }
Defensive patterns

Strategy: validation

Validate before calling

Set<CollapsedRequest<?, ?>> terminated = new HashSet<>();
// success path: request.setResponse(v); terminated.add(request);
// failure path: if (!terminated.contains(request)) request.setException(e);

Type guard

null

Try / catch

catch (IllegalStateException e) { if (e.getMessage().contains("exception can not be set")) { /* request already resolved — ignore duplicate error, log it */ } }

Prevention

When it happens

Trigger: setException() invoked after setResponse/setComplete/setException already terminated that request — e.g. generic catch-all loops that set exceptions on ALL requests including ones already satisfied earlier in the batch mapping.

Common situations: Batch command fails wholesale and code sets the exception on every request, while some requests already got responses from a partial-success path; retry/fallback flows inside the collapser that re-set the exception.

Related errors


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