eclipse-vertx/vert.x · error · IllegalStateException

Only the context thread can write a message

Error message

Only the context thread can write a message

What it means

Thrown by Http1ServerResponse.checkThread() when conn.strictThreadMode is enabled and a write/end call is made from a thread other than the request's context (event-loop) thread. Vert.x strict thread mode enforces that all response writes happen on the owning context to keep single-threaded guarantees.

Source

Thrown at vertx-core/src/main/java/io/vertx/core/http/impl/http1/Http1ServerResponse.java:107

                      Http1ServerConnection conn,
                      HttpRequest request,
                      Object requestMetric,
                      boolean keepAlive) {
    this.vertx = vertx;
    this.conn = conn;
    this.context = context;
    this.version = request.protocolVersion();
    this.headers = Http1xHeaders.httpHeaders();
    this.request = request;
    this.status = HttpResponseStatus.OK;
    this.requestMetric = requestMetric;
    this.keepAlive = keepAlive;
    this.head = request.method() == io.netty.handler.codec.http.HttpMethod.HEAD;
  }

  private void checkThread() {
    if (conn.strictThreadMode && !context.executor().inThread()) {
      throw new IllegalStateException("Only the context thread can write a message");
    }
  }

  @Override
  public MultiMap headers() {
    return headers;
  }

  @Override
  public MultiMap trailers() {
    if (trailers == null) {
      Http1xHeaders v = Http1xHeaders.httpHeaders();
      trailers = v;
      trailingHeaders = v;
    }
    return trailers;
  }

View on GitHub (pinned to fb308bd8c3)

Solutions

  1. Dispatch the write onto the request's context with context.runOnContext(v -> response.end()) or vertx.runOnContext
  2. Perform writes only inside Vert.x handlers (event loop), keep other threads for computation only
  3. Disable strictThreadMode if cross-thread writes are intentional (not recommended)

Example fix

// before
executor.submit(() -> response.end("done"));
// after
executor.submit(() -> context.runOnContext(v -> response.end("done")));
Defensive patterns

Strategy: try-catch

Validate before calling

// ensure writes run on the owning context
Context ctx = vertx.getOrCreateContext();
if (!ctx.equals(response.vertx().getOrCreateContext())) ctx.runOnContext(v -> response.end());

Try / catch

try { response.end(buf); } catch (IllegalStateException e) { context.runOnContext(v -> response.end(buf)); }

Prevention

When it happens

Trigger: Calling response.write(), response.end(), writeHead(), writeContinue(), writeEarlyHints() or netSocket() from a worker thread, a custom executor, or a user thread while the connection was created with strictThreadMode=true.

Common situations: Writing the response from a CompletableFuture callback or application thread pool; blocking worker doing the response; tests running code on the main thread.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


AI-assisted analysis of eclipse-vertx/vert.x@fb308bd8c3 (2026-09-06). Data as JSON: /api/errors/ed8f5c947a23307d. Report an issue: GitHub.