quarkusio/quarkus · error · RuntimeException

Async can only be started once

Error message

Async can only be started once

What it means

ResteasyReactiveRequestContext.setAsyncResponse attaches the JAX-RS AsyncResponse to the request context. Each request may start asynchronous processing only once; if an AsyncResponse is already attached, this RuntimeException is thrown to signal a duplicate suspension of the same request.

Source

Thrown at independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ResteasyReactiveRequestContext.java:674

        }
        return genericReturnType;
    }

    public ResteasyReactiveRequestContext setGenericReturnType(Type genericReturnType) {
        this.genericReturnType = genericReturnType;
        return this;
    }

    private static final String ASYNC_RESPONSE_PROPERTY_KEY = AbstractResteasyReactiveContext.CUSTOM_RR_PROPERTIES_PREFIX
            + "AsyncResponse";

    public AsyncResponseImpl getAsyncResponse() {
        return (AsyncResponseImpl) getProperty(ASYNC_RESPONSE_PROPERTY_KEY);
    }

    public ResteasyReactiveRequestContext setAsyncResponse(AsyncResponseImpl asyncResponse) {
        if (getAsyncResponse() != null) {
            throw new RuntimeException("Async can only be started once");
        }
        setProperty(ASYNC_RESPONSE_PROPERTY_KEY, asyncResponse);
        return this;
    }

    public ReaderInterceptor[] getReaderInterceptors() {
        return readerInterceptors;
    }

    public ResteasyReactiveRequestContext setReaderInterceptors(ReaderInterceptor[] readerInterceptors) {
        this.readerInterceptors = readerInterceptors;
        return this;
    }

    public WriterInterceptor[] getWriterInterceptors() {
        return writerInterceptors;
    }

View on GitHub (pinned to e1c734241f)

Solutions

  1. Ensure a single request invokes the async resource method only once; resume the existing AsyncResponse instead of creating a new one.
  2. If forwarding internally, complete the original request rather than re-entering the async endpoint with the same context.
  3. Check custom filters/interceptors for accidental re-dispatch to the same async resource.
  4. In tests, create a fresh request context per invocation instead of reusing the context object.

Example fix

// before
asyncResponse.resume(result);
service.callAgain(context); // re-enters setAsyncResponse
// after
asyncResponse.resume(result); // single suspension; resume the existing response only
Defensive patterns

Strategy: type-guard

Validate before calling

if (context.getAsyncResponse() != null) {
    // async already started: resume the existing response instead of suspending again
    return;
}

Type guard

AsyncResponseImpl existing = context.getAsyncResponse();
if (existing != null) {
    // already suspended: reuse `existing` rather than calling setAsyncResponse
    return existing;
}

Try / catch

try {
    context.setAsyncResponse(asyncResponse);
} catch (RuntimeException e) {
    if ("Async can only be started once".equals(e.getMessage())) {
        log.debug("Request already suspended; reusing existing AsyncResponse");
    } else {
        throw e;
    }
}

Prevention

When it happens

Trigger: A resource method returning AsyncResponse (or using @Suspended AsyncResponse) is invoked while the same request context already holds an async response — e.g. calling the async resource method twice within one request, or dispatching the same context through a filter/interceptor that re-enters the async method.

Common situations: Re-invoking a suspended resource method from a background thread; forwarding/rewriting a request to an async endpoint internally; misconfigured filters that re-dispatch; frameworks or tests reusing a request context across invocations.

Related errors


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