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
- Set the exception only on requests that have not yet been terminated; filter with a completed-set or check subject state via toObservable() materialization
- On batch failure, set exceptions OR responses, never both for the same request
- 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
- On batch failure, set exceptions only on unresolved requests
- Wrap per-request completion in a single idempotent helper method
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
- Response has already terminated so response can not be set :
- HystrixCollapser failed while executing.
- Not an event that can be converted to HystrixEventType.Colla
- Trying to start null batch which means it was shutdown alrea
- Collapser method must have one argument: {}
AI-assisted analysis of Netflix/Hystrix@5ce3bc58c3 (2026-08-14).
Data as JSON: /api/errors/3e7004122a7c2858.
Report an issue: GitHub.