Choosing a Vert.x Event Bus Delivery Pattern: Point-to-Point, Publish/Subscribe, or Request/Reply
A decision guide to Vert.x Event Bus delivery patterns — send, publish, and request — with a comparison table, backpressure and clustering trade-offs, and a runnable two-verticle validation example.
13 Sept 2025, 11:41 UTC

The decision you are actually making
When two verticles need to talk, Vert.x gives you one mechanism — the Event Bus — but three meaningfully different delivery patterns. Picking the wrong one usually shows up later as duplicated work (a message processed by every consumer when you wanted exactly one), silent message loss (a fire-and-forget send nobody consumed), or a distributed system you did not need (clustering enabled for a single-node app). This guide compares the supported patterns, states the constraints that should drive the choice, and ends with a minimal working example you can run to validate the behavior on your own version.
Assumptions: Vert.x 4.x with the core event bus (no bridge clients). Clustering requires a ClusterManager implementation on the classpath (Hazelcast is the historical default; Infinispan, Ignite, and Zookeeper managers also exist). Verify the manager artifact against your Vert.x version before relying on it.
Comparing the supported patterns
| Pattern | API | Delivery semantics | Best for |
|---|---|---|---|
| Point-to-point | eventBus.send(address, msg) | Exactly one consumer receives the message (round-robin if several are registered) | Distributing work across worker verticles |
| Publish/subscribe | eventBus.publish(address, msg) | Every registered consumer receives a copy | Events, cache invalidation, metrics fan-out |
| Request/reply | eventBus.request(address, msg) plus message.reply(...) | One consumer, with an asynchronous response and reply timeout | Service-style calls between verticles |
All three work identically in a single JVM and in a cluster; the only difference in clustered mode is that the consumer may live on another node. Note that Vert.x does not persist event bus messages: if no consumer is registered, a send fails with a NO_HANDLERS error and a publish silently delivers to nobody. If you need durability, bridge to a real broker (Kafka, AMQP) rather than stretching the event bus.
Trade-offs that matter in practice
Ordering. Messages from one sender to one consumer are generally delivered in send order, but clustering, node joins/leaves, and retries can disturb this. If ordering is business-critical, include a sequence number in the message body and verify behavior under your cluster manager.
Backpressure. The event bus itself is not a reactive-streams pipeline. A fast publisher can outpace a slow consumer and grow queues. Vert.x exposes MessageProducer with write() and drain-style flow control for this case; use it instead of raw send in hot loops.
Payloads and codecs. Strings, JSON, buffers, and primitives travel natively. Custom objects need a registered MessageCodec, and in clustered mode the default local-only delivery of unserializable objects will fail across nodes. Keep messages as JSON or plain data classes with an explicit codec.
Clustering is optional. Enabling a cluster manager adds network discovery, partition handling, and operational surface. If your verticles all live in one process, plain Vertx.vertx() is the correct choice — the event bus is fully functional without a cluster.
Concrete validation: one consumer vs. all consumers
The following two verticles demonstrate the difference between send and publish. Run them on your own machine; no cluster is required. This is an illustrative example — confirm the API signatures against the Vert.x version in your project.
// ConsumerVerticle.java — registers two consumers on "demo.address"
public class ConsumerVerticle extends AbstractVerticle {
@Override
public void start() {
vertx.eventBus().consumer("demo.address",
msg -> System.out.println("consumer-1 got: " + msg.body()));
vertx.eventBus().consumer("demo.address",
msg -> System.out.println("consumer-2 got: " + msg.body()));
}
}
// PublisherVerticle.java
public class PublisherVerticle extends AbstractVerticle {
@Override
public void start() {
// publish: BOTH consumers print
vertx.eventBus().publish("demo.address", "broadcast");
// send: exactly ONE consumer prints (round-robin)
vertx.eventBus().send("demo.address", "work-item-1");
vertx.eventBus().send("demo.address", "work-item-2");
}
}Deploy both from a main method with vertx.deployVerticle(...), or run via the Vert.x CLI if you have it installed: vertx run ConsumerVerticle.java in one terminal and the publisher in another with -cluster to observe cross-process delivery. Expected result: the publish line appears in both consumers; each send line appears in exactly one, alternating. If both consumers print every send, you accidentally used publish; if neither prints, check that deployment completed before publishing (deploy consumers first, then the publisher, or chain the futures).
How to check your setup
Three quick checks before committing to a pattern:
- Version features. Confirm the event bus classes and any bridge modules exist in your dependency tree (
mvn dependency:tree | grep vertxor the Gradle equivalent). Bridge connectors for AMQP, MQTT, or STOMP vary by version — check the Javadoc of your exact release rather than assuming a connector exists. - Failure behavior. Temporarily undeploy the consumer and call
send; you should see a reply failure withNO_HANDLERSonrequest/sendwith a reply handler. Decide explicitly whether your caller retries, drops, or dead-letters. - Cluster stability. If clustered, kill and restart a node during traffic and watch for reordering or lost messages. The event bus is at-most-once; design consumers to be idempotent.
The short version: use send for work distribution, publish for notifications, request when you need a response, and only add a cluster manager when verticles genuinely run in separate processes.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.