TL;DR:

  • Eclipse Zenoh (pronounced “zee-no”) is a unified data protocol that handles pub/sub, queries, and storage in a single API — from embedded microcontrollers to cloud infrastructure
  • It outperforms MQTT and DDS on latency and throughput in benchmarks, with a Rust core and bindings for Python, C, C++, Java, and more
  • Growing adoption in robotics (ROS2 bridge), autonomous vehicles, and industrial edge deployments where MQTT’s limitations are blocking

Most IoT data architectures end up stitching together multiple protocols: MQTT for sensor telemetry, REST/gRPC for queries, DDS for real-time robotics, and some kind of message bus in between. Each hop adds latency, complexity, and operational overhead. Eclipse Zenoh is a protocol designed to eliminate that stitching.

What Zenoh Is

Zenoh is an open protocol from the Eclipse Foundation, originally developed by ADLINK Technology and now maintained by the wider community. The core implementation is in Rust; bindings exist for Python, C, C++, Java/Kotlin, TypeScript/JavaScript (via WASM), and a C extension for embedded targets like microcontrollers.

The key claim is unified data handling across three operations:

  1. Publication — streaming sensor data, telemetry, video frames
  2. Subscription — consuming streams in real time
  3. Queries — requesting historical data or on-demand reads from storage

In traditional architectures, you’d use MQTT for (1) and (2), then a separate time-series database API for (3). Zenoh handles all three through a single abstraction.

How Zenoh Differs from MQTT

MQTT is a publish/subscribe protocol with a central broker. Zenoh has no mandatory broker — it supports peer-to-peer topology, router-based topology, and hybrid configurations. This matters for edge deployments where a central broker is a single point of failure, or for constrained networks where broker overhead is significant.

Performance differences in independent benchmarks:

MetricMQTT (Mosquitto)Zenoh
Median latency (LAN)~500µs~50µs
Throughput (1KB msgs)~50k msg/s~500k msg/s
Memory footprintBroker: ~5MBRouter: ~3MB
Embedded footprintN/A (broker-side only)<100KB on MCU

The throughput difference becomes significant for robotics applications streaming LiDAR point clouds (large payloads at high frequency) or industrial vision systems processing camera frames.

Zenoh’s Resource Key Expressions

Zenoh uses a hierarchical key expression syntax similar to MQTT topics, but with more flexible matching:

sensors/factory1/line2/temperature    # Specific key
sensors/*/line2/*                     # Wildcard: any factory, any sensor
sensors/factory1/**/temperature       # Multi-level: any depth

Unlike MQTT wildcards which only work on subscriptions, Zenoh key expressions work uniformly across pub/sub and queries, making APIs consistent.

Python Quick Start

import zenoh
import json
import time

# Publisher
with zenoh.open(zenoh.Config()) as session:
    pub = session.declare_publisher("sensors/factory1/temp")
    
    while True:
        data = {"value": 23.4, "unit": "celsius", "ts": time.time()}
        pub.put(json.dumps(data).encode())
        time.sleep(1.0)
# Subscriber
import zenoh

def on_sample(sample):
    key = sample.key_expr
    value = json.loads(sample.payload.decode())
    print(f"{key}: {value}")

with zenoh.open(zenoh.Config()) as session:
    sub = session.declare_subscriber("sensors/**", on_sample)
    
    # Block forever
    import time
    while True:
        time.sleep(1)

Queryable: On-Demand Data Requests

This is where Zenoh diverges most from MQTT. A Queryable registers a handler for data queries, effectively making any node capable of responding to “what is the current state?” without a separate REST endpoint:

# Register a queryable for on-demand reads
with zenoh.open(zenoh.Config()) as session:
    latest_readings = {}  # In-memory state
    
    def on_query(query):
        key = str(query.key_expr)
        if key in latest_readings:
            query.reply(
                zenoh.Sample(query.key_expr, 
                           json.dumps(latest_readings[key]).encode())
            )
    
    qbl = session.declare_queryable("sensors/**", on_query)
    
    # Elsewhere in the code
    sub = session.declare_subscriber("sensors/**", 
                                     lambda s: latest_readings.update(
                                         {str(s.key_expr): 
                                          json.loads(s.payload.decode())}))

On the query side:

# Query current state without maintaining a persistent subscription
with zenoh.open(zenoh.Config()) as session:
    replies = session.get("sensors/factory1/**", zenoh.Queue())
    
    for reply in replies.receiver:
        if isinstance(reply, zenoh.Sample):
            print(f"Got: {reply.key_expr} = {reply.payload.decode()}")

ROS2 Integration

Zenoh has an official ROS2 bridge (zenoh-plugin-ros2dds) that replaces the default DDS transport. For multi-robot systems or ROS2 nodes communicating across network segments, this is significantly simpler than configuring DDS discovery across subnets:

# Install the bridge
cargo install zenoh-bridge-ros2dds

# Run the bridge — it autodiscovers local ROS2 topics
zenoh-bridge-ros2dds --config bridge_config.json

This makes cross-robot communication as simple as publishing to a Zenoh key expression instead of managing DDS participant discovery across network namespaces.

Embedded Deployment

The Zenoh pico library targets no-OS or RTOS environments (FreeRTOS, Zephyr, bare metal). It has no dynamic allocation requirement and can operate within 50KB of flash and 10KB of RAM — viable for ESP32, STM32, and similar microcontrollers.

// Zenoh pico on an embedded target (C API)
#include <zenoh-pico.h>

z_owned_config_t config = z_config_default();
zp_config_insert(z_loan(config), Z_CONFIG_CONNECT_KEY, 
                 z_string_make("tcp/192.168.1.100:7447"));

z_owned_session_t session = z_open(z_move(config));

z_owned_publisher_t pub = z_declare_publisher(
    z_loan(session),
    z_keyexpr("sensor/node1/temp"),
    NULL
);

float temperature = read_sensor();
z_publisher_put(z_loan(pub), 
                (const uint8_t*)&temperature, sizeof(float), NULL);

The same data flows into the same Zenoh network as Python subscribers running on an edge server — no protocol translation, no gateway.

When to Choose Zenoh

Use Zenoh when:

  • You need low-latency (sub-millisecond) pub/sub across edge nodes
  • You’re building a ROS2-based robotics system that needs multi-network communication
  • You want unified pub/sub + query without separate APIs for streaming vs. historical data
  • You’re deploying on constrained hardware where MQTT broker overhead is problematic

Stick with MQTT when:

  • Your team knows it and it’s working — operational familiarity is worth a lot
  • Your stack already has good MQTT broker tooling (EMQX, HiveMQ, Mosquitto) and you don’t need query semantics
  • Your IoT platform (AWS IoT Core, Azure IoT Hub) has native MQTT integration you want to use

Zenoh is production-ready — it’s deployed in automotive (Tier 1 suppliers), industrial automation, and drone fleets — but it’s less mature than MQTT in terms of hosted broker services and vendor ecosystem. The Rust SDK is the most complete; Python and C++ bindings are stable. JavaScript bindings via WASM work but have higher overhead.