quarkusio/quarkus · error · IllegalStateException
Request already suspended
Error message
Request already suspended
What it means
VertxHttpRequest.suspend(time, unit) implements the JAX-RS AsyncResponse suspend contract: a request can be suspended only once. If suspend is called when wasSuspended is already true it throws IllegalStateException("Request already suspended"), mirroring the Servlet spec's rule against double suspension.
Source
Thrown at extensions/resteasy-classic/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/standalone/VertxHttpRequest.java:223
@Override
public ResteasyAsynchronousResponse getAsyncResponse() {
return asyncResponse;
}
@Override
public ResteasyAsynchronousResponse suspend() throws IllegalStateException {
return suspend(-1);
}
@Override
public ResteasyAsynchronousResponse suspend(long millis) throws IllegalStateException {
return suspend(millis, TimeUnit.MILLISECONDS);
}
@Override
public ResteasyAsynchronousResponse suspend(long time, TimeUnit unit) throws IllegalStateException {
if (wasSuspended) {
throw new IllegalStateException("Request already suspended");
}
wasSuspended = true;
return asyncResponse;
}
@Override
public void complete() {
if (wasSuspended && asyncResponse != null)
asyncResponse.complete();
}
@Override
public CompletionStage<Void> executeAsyncIo(CompletionStage<Void> f) {
// check if this CF is already resolved
CompletableFuture<Void> ret = f.toCompletableFuture();
// if it's not resolved, we may need to suspend
if (!ret.isDone() && !isSuspended()) {
suspend();View on GitHub (pinned to e1c734241f)
Solutions
- Call suspend exactly once per request; track your own boolean or rely on the AsyncResponse returned from the first call.
- Always resume()/cancel() the AsyncResponse when done so downstream logic doesn't attempt re-suspension.
- If you need a timeout extension, use setTimeout(long, TimeUnit) instead of calling suspend again.
- Move suspension to a single place (filter or resource, not both).
Example fix
// before request.suspend(); request.suspend(5, TimeUnit.SECONDS); // IllegalStateException // after AsyncResponse async = request.suspend(5, TimeUnit.SECONDS); async.setTimeout(10, TimeUnit.SECONDS); // adjust timeout instead of re-suspending
Defensive patterns
Strategy: validation
Validate before calling
if (asyncResponse != null && !asyncResponse.isDone() && !asyncResponse.isCancelled() && suspended) {
throw new IllegalStateException("suspend() already called for this request");
} Try / catch
try {
asyncResp = request.suspend(5, TimeUnit.SECONDS);
} catch (IllegalStateException e) {
// already suspended: reuse existing asyncResp instead of re-suspending
asyncResp.setTimeout(5, TimeUnit.SECONDS);
} Prevention
- Suspend exactly once per request, in a single well-defined place.
- Always resume() or cancel() the AsyncResponse when finished.
- Use setTimeout() to change timeouts instead of calling suspend again.
- Track suspension state with your own flag in filters that may run multiple times.
When it happens
Trigger: Calling request.suspend(...) / asyncResponse-based suspension twice on the same request — e.g. calling suspend() after a previous suspend() or suspend(long, TimeUnit) without resuming/completing the prior AsyncResponse, or invoking suspend again in a filter and again in the resource.
Common situations: Manual async JAX-RS code where both a ContainerRequestFilter and the resource call suspend; retry logic that re-suspends after resume was not called; copying Servlet-style suspend code that allowed re-arm in another container.
Related errors
- Response has been closed
- Broadcaster has been closed
- Already closed
- Already registered on a broadcaster
- Extra steps left over
AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05).
Data as JSON: /api/errors/574a3ca297964562.
Report an issue: GitHub.