eclipse-vertx/vert.x · error · IllegalStateException

This operation must be called from a Vert.x thread

Error message

This operation must be called from a Vert.x thread

What it means

InboundBuffer methods such as write() must run on a Vert.x thread owned by the buffer's context; checkThread() enforces this by testing context.inThread(). The buffer mutates its pending queue and demand non-thread-safely, so off-thread calls would corrupt state, and Vert.x throws IllegalStateException instead.

Source

Thrown at vertx-core/src/main/java/io/vertx/core/streams/impl/InboundBuffer.java:103

  }

  public InboundBuffer(Context context, long highWaterMark) {
    if (context == null) {
      throw new NullPointerException("context must not be null");
    }
    if (highWaterMark < 0) {
      throw new IllegalArgumentException("highWaterMark " + highWaterMark + " >= 0");
    }
    this.context = (ContextInternal) context;
    this.highWaterMark = highWaterMark;
    this.demand = Long.MAX_VALUE;
    // empty ArrayDeque's constructor ArrayDeque allocates 16 elements; let's delay the allocation to be of the proper size
    this.pending = null;
  }

  private void checkThread() {
    if (!context.inThread()) {
      throw new IllegalStateException("This operation must be called from a Vert.x thread");
    }
  }

  /**
   * Write an {@code element} to the buffer. The element will be delivered synchronously to the handler when
   * it is possible, otherwise it will be queued for later delivery.
   *
   * @param element the element to add
   * @return {@code false} when the producer should stop writing
   */
  public boolean write(E element) {
    checkThread();
    Handler<E> handler;
    synchronized (this) {
      if (demand == 0L || emitting) {
        if (pending == null) {
          pending = new ArrayDeque<>(1);
        }

View on GitHub (pinned to fb308bd8c3)

Solutions

  1. Wrap the write in the buffer's context: context.runOnContext(v -> inboundBuffer.write(element))
  2. Dispatch from the third-party callback via vertx.runOnContext or a handler invoked on a Vert.x thread
  3. If in a Worker/startThread scope, ensure the code executes on a context thread (vertx.executeBlocking with the right context)

Example fix

// before
externalClient.onMessage(msg -> inboundBuffer.write(msg));
// after
externalClient.onMessage(msg ->
  context.runOnContext(v -> inboundBuffer.write(msg)));
Defensive patterns

Strategy: try-catch

Validate before calling

if (ctx != null && !ctx.isRunningOnContext()) {
  ctx.runOnContext(v -> inboundBuffer.write(element));
} else {
  inboundBuffer.write(element);
}

Try / catch

try {
  inboundBuffer.write(element);
} catch (IllegalStateException e) {
  if (e.getMessage().contains("Vert.x thread")) {
    context.runOnContext(v -> inboundBuffer.write(element));
  } else throw e;
}

Prevention

When it happens

Trigger: Calling inboundBuffer.write(element) (or fetch/pause/resume/draining paths that delegate to write) from a plain application thread, a third-party callback thread (e.g. a JDBC or MQTT client thread), or a different Vert.x context's event-loop/worker thread than the one the buffer was created with.

Common situations: Writing into a ReadStream's buffer directly from a custom client's network thread; delivering results from an executor instead of ctx.runOnContext; accessing the stream after its context/worker shut down.

Related errors


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