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

  1. Call suspend exactly once per request; track your own boolean or rely on the AsyncResponse returned from the first call.
  2. Always resume()/cancel() the AsyncResponse when done so downstream logic doesn't attempt re-suspension.
  3. If you need a timeout extension, use setTimeout(long, TimeUnit) instead of calling suspend again.
  4. 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

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


AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05). Data as JSON: /api/errors/574a3ca297964562. Report an issue: GitHub.