quarkusio/quarkus · error · IllegalStateException

Cannot write more than one piece of async data at a time

Error message

Cannot write more than one piece of async data at a time

What it means

ServletRequestContext (the servlet bridge for RESTEasy Reactive) only supports one outstanding asynchronous write on the servlet output stream at a time. write() throws this IllegalStateException, inside a synchronized block, if another async write (asyncWriteData) is still pending. It protects against interleaving blocking and async response writes.

Source

Thrown at extensions/resteasy-reactive/rest-servlet/runtime/src/main/java/io/quarkus/resteasy/reactive/server/servlet/runtime/ServletRequestContext.java:516

        return this;
    }

    @Override
    public ServerHttpResponse write(byte[] data, Consumer<Throwable> asyncResultHandler) {
        if (asyncWriteData != null) {
            asyncResultHandler.accept(new IllegalStateException("Cannot write before data has all been written"));
        }
        if (asyncContext == null) {
            try {
                response.getOutputStream().write(data);
                asyncResultHandler.accept(null);
            } catch (IOException e) {
                asyncResultHandler.accept(e);
            }
        } else {
            synchronized (this) {
                if (asyncWriteData != null) {
                    throw new IllegalStateException("Cannot write more than one piece of async data at a time");
                }
                asyncWriteData = data;
                asyncWriteHandler = asyncResultHandler;
                if (writeListener == null) {
                    try {
                        ServletOutputStream outputStream = response.getOutputStream();
                        outputStream.setWriteListener(writeListener = new ServletWriteListener(outputStream));
                    } catch (IOException e) {
                        asyncResultHandler.accept(e);
                    }
                } else {
                    writeListener.onWritePossible();
                }
            }
        }
        return this;
    }

View on GitHub (pinned to e1c734241f)

Solutions

  1. Wait for the asyncResultHandler callback (success or failure) before issuing the next write
  2. Serialize writes through a single thread or an orderly queue so only one write is in flight
  3. On callback completion, clear asyncWriteData state (the implementation does this) before continuing
  4. If streaming large payloads, use a blocking-safe write loop on a worker thread instead of stacked async writes

Example fix

// before
ctx.write(chunk1, handler);
ctx.write(chunk2, handler); // throws
// after
ctx.write(chunk1, handler);
handler.thenRun(() -> ctx.write(chunk2, handler));
Defensive patterns

Strategy: try-catch

Validate before calling

synchronized (ctx) {
    boolean safe = (pendingWrites.get(ctx) == 0); // track in-flight writes externally
}
// only issue next write when previous asyncResultHandler completed

Type guard

boolean canWrite(ServletRequestContext ctx) {
    return !ctxHasPendingAsyncWrite(ctx); // maintain your own in-flight flag
}

Try / catch

try {
    ctx.write(data, handler);
} catch (IllegalStateException e) {
    queue.offer(data); // retry after current write completes
}

Prevention

When it happens

Trigger: Calling write(byte[], io.quarkus.vertx.core.runtime.context.SafeWritableOutputStream-like handler) while a previous async write has not completed; calling write twice before the write listener reports completion; concurrent writes from multiple threads on the same response context.

Common situations: Custom response streaming code writing chunks without awaiting the async result handler; servlet async write listener not invoked (client slow), so the next write hits the pending slot; mixing blocking and async output APIs on the same request.

Related errors


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