Vert.x Event Bus with JSON Codec: Architecture Note for Microservices
A custom JSON codec and worker verticles keep the Vert.x Event Bus responsive, but clustered delivery introduces silent failure modes and a hard 256 KB payload limit.
28 Jun 2026, 11:01 UTC

The problem with default JSON on the Event Bus
When you decompose a Vert.x application into multiple verticles, the Event Bus is the natural transport. The built-in JSON codec is easy to adopt, but it serializes null fields and offers no compile-time contract. In a microservices layout where consumer verticles evolve independently, that combination produces brittle parsing and silent schema drift. A small custom JSON codec, paired with a worker verticle for blocking work, keeps the event loop responsive and gives you a typed, debuggable boundary. The trade-off is that the Event Bus remains volatile and cluster-sensitive: a misconfigured cluster manager can drop or duplicate messages without raising an obvious error.
Requirements for inter-verticle messaging
- Deliver messages between verticles on the same JVM and across a cluster without blocking the event loop.
- Enforce a payload size ceiling before serialization to avoid
MessageTooLargeExceptionat runtime. - Keep JSON schemas stable so consumers can evolve without coordinated releases.
- Detect cluster membership and message loss during operation.
Smallest suitable design: codec, producer, consumer
The smallest design that meets these requirements uses three parts:
- A custom message codec that maps domain objects to
JsonObjectand back, omitting null fields to reduce drift. - A producer verticle that validates payload size and publishes to a well-known address.
- A consumer verticle registered on that address. If the consumer performs blocking I/O or CPU-heavy work, deploy it as a worker verticle so the event loop stays free.
For clustered delivery, every node must share the same cluster manager configuration (Hazelcast, Infinispan, or Redis). A mismatch here is the most common cause of silent partition behavior.
Example: custom codec and payload guard
The following pattern shows a codec that skips null fields and a producer that checks the serialized size before sending. Run this inside a Vert.x verticle or a test class; it requires vertx-core and a JSON library such as Jackson on the classpath.
public class OrderCodec implements MessageCodec<Order, Order> {
@Override
public void encodeToWire(Buffer buffer, Order order) {
String json = Json.encode(order); // custom encoder that omits nulls
buffer.appendInt(json.length());
buffer.appendString(json);
}
@Override
public Order decodeFromWire(int pos, Buffer buffer) {
int len = buffer.getInt(pos);
String json = buffer.getString(pos + 4, pos + 4 + len);
return Json.decodeValue(json, Order.class);
}
@Override
public Order transform(Order order) { return order; }
@Override
public String name() { return "order-codec"; }
@Override
public byte systemCodecID() { return -1; }
}Producer guard:
public void sendOrder(Order order) {
JsonObject payload = order.toJson(); // assumes nulls omitted
byte[] bytes = payload.encode().getBytes(StandardCharsets.UTF_8);
if (bytes.length > 256 * 1024) {
// handle locally; do not call eventBus.send
throw new PayloadTooLargeException("Order exceeds 256 KB event bus limit");
}
eventBus.send("orders.new", order, new DeliveryOptions().setCodecName("order-codec"));
}Trust and data boundaries
Treat the Event Bus as a trusted internal network, not as an external API boundary. JSON payloads arriving from other verticles are still untrusted input: validate them before use. The default 256 KB message limit is a hard boundary; enforce it in application code so producers fail fast rather than triggering a MessageTooLargeException inside the bus. If you expose the same addresses to external HTTP or TCP clients, add authentication and per-client rate limiting at the edge, because the Event Bus itself has no built-in security model.
Operational checks after deployment
After deploying a clustered producer and consumer, verify three things:
- Cluster membership. In any verticle, inspect
vertx.isClustered()and enumerate nodes viavertx.sharedData().getClusterManager().getNodes(). If clustering is disabled or the node set is incomplete, the cluster manager is misconfigured. - Delivery and latency. Publish 1,000 or more messages and compare the consumer count against the producer count. Measure end-to-end latency to confirm the event loop is not blocked; a sudden latency spike usually indicates blocking code on the event loop rather than a bus problem.
- Size limit. Send a payload just over 256 KB and confirm the bus rejects it. The producer-side guard should make this a non-event, but the check proves the boundary is active.
Failure modes to monitor
- Silent drop or duplication. An incorrectly configured cluster manager can partition the network. A producer may believe a message was sent while a consumer never receives it, or a consumer may process the same message twice after a node rejoins. Monitor consumer lag and deduplicate by message ID when correctness matters.
- Schema drift. The default JSON codec serializes null fields. If a consumer adds a required field and a producer omits it, the consumer may receive an explicit
nullinstead of a missing key, causing brittle parsing. A custom codec that omits nulls reduces this risk. - Blocking the event loop. Long-running work inside a standard verticle blocks all other handlers on that loop. Offload blocking calls to
executeBlockingor a worker verticle.
When to abandon this design
- Durability is required. The Event Bus is volatile. If message loss is unacceptable—for example, financial transactions or audit logs—add Kafka, NATS, or another durable broker. The Event Bus can remain for fast local signaling, while the broker handles at-least-once delivery.
- Strict cross-team contracts. If multiple teams or languages consume the same messages, JSON null-handling becomes a liability. Move to Protobuf or Avro with an explicit schema registry.
- Payloads routinely exceed 256 KB. Either chunk the data and send references, or bypass the Event Bus for bulk transfer using shared data or direct TCP.
Limitations and a practical smoke test
This design assumes a stable, low-latency network between cluster nodes and a cluster manager that is correctly configured on every instance. Because cluster manager behavior varies by version and provider, verify the exact node-enumeration API and default message size in your Vert.x version before relying on them in production. A practical smoke test is to deploy two nodes, send a known sequence of messages, and assert that the consumer receives exactly that sequence with no duplicates and no gaps.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.