Using Vert.x Event Bus Request‑Response for Asynchronous Service Calls
Learn how to use Vert.x Event Bus request‑response for non‑blocking service‑to‑service calls, with a working Java example, limits, and common pitfalls.
15 Jul 2026, 04:50 UTC

Quick answer
To invoke another Vert.x service without blocking the event loop, send a request on the event bus and attach a reply handler. The caller receives the response asynchronously, keeping the loop free for other work.
How the request‑reply pattern works
Vert.x provides a point‑to‑point messaging abstraction called the EventBus. A request sends a message to a specific address and expects a reply from a consumer registered on that address. The reply is delivered to a handler you supply, which runs on the event loop but does not block it.
Key steps
- Register a consumer on an address (e.g.,
"orderservice").
- In the consumer, inspect the message body and call
msg.reply(...) when ready.
- From the caller, invoke
eventBus.request(address, payload, handler).
- The handler receives an
AsyncResult<Message<T>>; on success you read result().body(), on failure you inspect cause().
"orderservice").msg.reply(...) when ready.eventBus.request(address, payload, handler).AsyncResult<Message<T>>; on success you read result().body(), on failure you inspect cause().Worked example (Java)
The following snippet shows a simple order‑validation service and a caller that asks whether an order ID is valid.
import io.vertx.core.Vertx;
import io.vertx.core.eventbus.EventBus;
import io.vertx.core.Handler;
import io.vertx.core.AsyncResult;
public class OrderServiceExample {
public static void main(String[] args) {
Vertx vertx = Vertx.vertx();
EventBus eb = vertx.eventBus();
// 1️⃣ Consumer: validates order IDs
eb.consumer("orderservice", msg -> {
String orderId = msg.body();
// Simulate a quick check (non‑blocking)
boolean valid = orderId.matches("\\d+");
String reply = valid ? "OK-" + orderId : "INVALID";
msg.reply(reply);
});
// 2️⃣ Caller: sends a request and handles the reply
eb.request("orderservice", "12345", ar -> {
if (ar.succeeded()) {
System.out.println("Reply: " + ar.result().body());
} else {
System.err.println("Failure: " + ar.cause());
}
// Clean up – close the Vertx instance when done (optional for demo)
vertx.close();
});
}
}
When you run this program you should see output similar to:
Reply: OK-12345
No error output appears because the consumer processed the request and replied within the default 30‑second timeout.
Limits and considerations
Scope of delivery
The request‑reply works within a single Vert.x instance by default. To reach consumers on other JVMs you must enable a clustered event bus (e.g., with Hazelcast, Infinispan, or the Vert.x Cluster Manager). Clustering adds network latency and requires that message payloads be serializable on all nodes.
Message types
Allowed payload types are String, byte[], Buffer, JsonObject, JsonArray, or any POJO that Vert.x can serialize (default JSON). If you pass a custom class, ensure the same class definition and library versions exist on every node; otherwise you may see ClassCastException or silent message loss.
Timeouts
The request method uses a default timeout of 30 seconds. If no reply arrives within that period the handler fails with a TimeoutException. You can override this by passing a DeliveryOptions object:
DeliveryOptions opts = new DeliveryOptions().setTimeout(5000); // 5 seconds
eb.request("orderservice", "9999", opts, ar -> { … });
Ordering guarantees
Vert.x does not guarantee that replies will arrive in the same order as the requests were sent, especially when multiple concurrent requests are in flight. If ordering is required, include a correlation ID in the request and match it in the reply handler.
Common mistakes and how to avoid them
- Blocking the event loop inside a consumer. Performing long‑running I/O or
Thread.sleep()in the consumer handler stalls the loop and hurts throughput. Offload such work to a worker verticle viavertx.executeBlocking()or deploy the consumer as a worker verticle. - Assuming the reply runs on the caller’s thread. The reply handler is invoked on the event loop, not necessarily the thread that issued the request. Do not store thread‑local state expecting it to persist across the request‑reply boundary.
- Forgetting to set a timeout. Relying on the default 30‑second timeout can mask programming errors where a consumer never replies. Explicitly set a timeout that matches your service’s expected latency and log failures.
- Using incompatible serialization in a cluster. When clustering, verify that all nodes use the same JSON library (e.g., Jackson vs. Gson) and the same version of any custom POJOs. A mismatch can cause deserialization exceptions that are swallowed silently, leading to missed replies.
Practical verification steps
- Local single‑node test. Run the example code above in a IDE or with
mvn exec:java. Confirm the console prints the expected reply and no stack traces. - Unit test with Vert.x JUnit5 extension. Deploy a verticle that registers the consumer, send a request via
eventBus.request(), and assert that the handler receives the correct payload within a short timeout (e.g., 1 second). - Clustered test. Start two JVMs with the Hazelcast cluster manager (
-Dvertx.clusterManager=io.vertx.spi.cluster.hazelcast.HazelcastClusterManager). Register the consumer on node A, send a request from node B, and verify the reply is received. Then deliberately change a POJO field on one node and observe the failure to confirm serialization sensitivity.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.