Using Custom Message Codecs in Vert.x Event Bus to Reduce JSON Overhead
Learn how to register a custom MessageCodec in Vert.x Event Bus to send domain objects without JSON overhead, with a full example and pitfalls to avoid.
12 Jan 2026, 20:32 UTC

Why use a custom codec?
The Vert.x Event Bus defaults to JSON for message payloads. JSON is convenient but adds parsing overhead and produces larger byte arrays than a binary representation of the same data. When you frequently exchange domain objects (e.g., an Order POJO) between verticles, registering a dedicated MessageCodec lets Vert.x encode and decode the object directly, avoiding JSON serialization and deserialization costs.
How it works – a worked example
Below is a minimal, self‑contained example that demonstrates the full lifecycle: define a POJO, implement a codec, register the codec before any message is sent, deploy a sender verticle and a receiver verticle, and exchange an Order instance over the address "orders".
1. Define the message type
public class Order {
private final String id;
private final String product;
private final int quantity;
public Order(String id, String product, int quantity) {
this.id = id;
this.product = product;
this.quantity = quantity;
}
// getters omitted for brevity
@Override
public String toString() {
return "Order{id='" + id + "', product='" + product + "', quantity=" + quantity + "}";
}
}
2. Implement the codec
The codec converts an Order to a byte array and back. For simplicity we use UTF‑8 CSV format; in production you might choose Protobuf, Avro, or a custom binary layout.
import io.vertx.core.buffer.Buffer;
import io.vertx.core.eventbus.MessageCodec;
public class OrderCodec implements MessageCodec {
@Override
public void encodeToWire(Buffer buffer, Order order) {
// id,product,quantity\n" + "\n"
String csv = order.getId() + ',' + order.getProduct() + ',' + order.getQuantity() + '\n';
buffer.appendString(csv);
}
@Override
public Order decodeFromWire(int pos, Buffer buffer) {
// read until the newline delimiter
int start = pos;
int end = buffer.getByte(pos) == -1 ? pos : buffer.indexOf(pos, (byte)'\n');
if (end == -1) { throw new IllegalArgumentException("Incomplete message"); }
String csv = buffer.getString(start, end - start);
String[] parts = csv.split(',');
return new Order(parts[0], parts[1], Integer.parseInt(parts[2]));
}
@Override
public Order transform(Order order) {
// No transformation needed; return the same instance
return order;
}
@Override
public String name() {
return "order-codec";
}
@Override
public byte systemCodecID() {
return -1; // -1 indicates a user‑defined codec
}
}
3. Register the codec before any message is sent
Registration must happen on the Vertx instance and is global for the JVM. If a message is sent to an address before the codec is registered, Vert.x throws MessageCodecNotFoundException.
import io.vertx.core.Vertx;
public class Main {
public static void main(String[] args) {
Vertx vertx = Vertx.vertx();
// Register the codec globally
vertx.eventBus().registerDefaultCodec(Order.class, new OrderCodec());
// Now it is safe to deploy verticles that will send/receive Orders
vertx.deployVerticle(new OrderSenderVerticle());
vertx.deployVerticle(new OrderReceiverVerticle());
}
}
4. Sender verticle
import io.vertx.core.AbstractVerticle;
import io.vertx.core.eventbus.EventBus;
public class OrderSenderVerticle extends AbstractVerticle {
@Override
public void start() {
EventBus eb = vertx.eventBus();
// Send an Order every second
vertx.setPeriodic(1000, timer -> {
Order order = new Order("ORD-" + timer, "Widget", 2);
eb.send("orders", order);
System.out.println("Sent: " + order);
});
}
}
5. Receiver verticle
import io.vertx.core.AbstractVerticle;
import io.vertx.core.eventbus.Message;
public class OrderReceiverVerticle extends AbstractVerticle {
@Override
public void start() {
vertx.eventBus().consumer("orders", (Message msg) -> {
Order order = msg.body();
System.out.println("Received: " + order);
// Important: avoid blocking work here; if needed, delegate to a worker verticle
});
}
}
Limits and common mistakes
- Registration timing – The codec must be registered before the first message is sent on any address that expects that type. Registering inside a verticle after it has already sent a message will cause
MessageCodecNotFoundException. The safest place is during Vert.x startup, as shown in theMainclass. - Global scope – A codec registration applies to the entire Vert.x instance. You cannot have two different codecs for the same Java type simultaneously; attempting to register a second codec replaces the first.
- Payload size – Even with a custom codec, extremely large objects can still block the event loop if encoded/decoded on the event‑loop thread. For large payloads consider:
- Splitting the message into chunks and reassembling.
- Using a worker verticle for the heavy encode/decode work (e.g.,
vertx.executeBlocking). - Blocking handlers – Performing blocking operations (e.g., DB calls, file I/O) directly inside the Event Bus handler stalls the event loop. Offload such work to a worker verticle or use
executeBlocking. - Versioning – If the structure of
Orderchanges, older codecs may fail to decode newer bytes. Include a version field in your encoded format or use a schema‑evolution‑friendly serialization framework.
Verification checklist
- Start the application and observe the console: you should see alternating “Sent: …” and “Received: …” lines for each periodic tick.
- Confirm that no
MessageCodecNotFoundExceptionappears in the logs. - Optionally, enable Event Bus metrics (
vertx.eventBus().metrics()) and verify that the message count increases and latency stays low (sub‑millisecond for small objects).
By following the steps above you replace JSON serialization with a lightweight, type‑safe codec, reducing CPU usage and garbage‑collection pressure while preserving the simplicity of the Vert.x Event Bus programming model.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.