{"record":{"id":"d4fbe0b78cc8f5da","repo":"quarkusio/quarkus","slug":"async-can-only-be-started-once","errorCode":null,"errorMessage":"Async can only be started once","messagePattern":"Async can only be started once","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ResteasyReactiveRequestContext.java","lineNumber":674,"sourceCode":"        }\n        return genericReturnType;\n    }\n\n    public ResteasyReactiveRequestContext setGenericReturnType(Type genericReturnType) {\n        this.genericReturnType = genericReturnType;\n        return this;\n    }\n\n    private static final String ASYNC_RESPONSE_PROPERTY_KEY = AbstractResteasyReactiveContext.CUSTOM_RR_PROPERTIES_PREFIX\n            + \"AsyncResponse\";\n\n    public AsyncResponseImpl getAsyncResponse() {\n        return (AsyncResponseImpl) getProperty(ASYNC_RESPONSE_PROPERTY_KEY);\n    }\n\n    public ResteasyReactiveRequestContext setAsyncResponse(AsyncResponseImpl asyncResponse) {\n        if (getAsyncResponse() != null) {\n            throw new RuntimeException(\"Async can only be started once\");\n        }\n        setProperty(ASYNC_RESPONSE_PROPERTY_KEY, asyncResponse);\n        return this;\n    }\n\n    public ReaderInterceptor[] getReaderInterceptors() {\n        return readerInterceptors;\n    }\n\n    public ResteasyReactiveRequestContext setReaderInterceptors(ReaderInterceptor[] readerInterceptors) {\n        this.readerInterceptors = readerInterceptors;\n        return this;\n    }\n\n    public WriterInterceptor[] getWriterInterceptors() {\n        return writerInterceptors;\n    }\n","sourceCodeStart":656,"sourceCodeEnd":692,"githubUrl":"https://github.com/quarkusio/quarkus/blob/e1c734241f34c7919086ceb4c9262b4a58f6de44/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ResteasyReactiveRequestContext.java#L656-L692","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Ensure a single request invokes the async resource method only once; resume the existing AsyncResponse instead of creating a new one.","If forwarding internally, complete the original request rather than re-entering the async endpoint with the same context.","Check custom filters/interceptors for accidental re-dispatch to the same async resource.","In tests, create a fresh request context per invocation instead of reusing the context object."],"exampleFix":"// before\nasyncResponse.resume(result);\nservice.callAgain(context); // re-enters setAsyncResponse\n// after\nasyncResponse.resume(result); // single suspension; resume the existing response only","handlingStrategy":"type-guard","validationCode":"if (context.getAsyncResponse() != null) {\n    // async already started: resume the existing response instead of suspending again\n    return;\n}","typeGuard":"AsyncResponseImpl existing = context.getAsyncResponse();\nif (existing != null) {\n    // already suspended: reuse `existing` rather than calling setAsyncResponse\n    return existing;\n}","tryCatchPattern":"try {\n    context.setAsyncResponse(asyncResponse);\n} catch (RuntimeException e) {\n    if (\"Async can only be started once\".equals(e.getMessage())) {\n        log.debug(\"Request already suspended; reusing existing AsyncResponse\");\n    } else {\n        throw e;\n    }\n}","preventionTips":["Invoke async resource methods exactly once per request.","Do not re-dispatch or forward requests into the same async endpoint.","Avoid reusing ResteasyReactiveRequestContext across invocations in tests.","Review filters/interceptors for accidental re-entry into async resources."],"tags":["quarkus","resteasy-reactive","async","asyncresponse","request-lifecycle"],"backgroundTag":"async-already-started","analyzedSha":"e1c734241f34c7919086ceb4c9262b4a58f6de44","analyzedAt":"2026-09-05T17:01:29.979Z","contentChangedAt":"2026-09-05T17:01:29.979Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}