Sep 5, 2026 · zenoh · dds · ros2 · middleware-comparison
Zenoh vs DDS in ROS 2: What Changed and Why People Care
Zenoh changed how ROS 2 nodes find each other, and DDS is still the right default on a wired network. Here is which one belongs on your robot.
Zenoh is now a supported ROS 2 middleware that changes how nodes find each other, but DDS stays right when machines share a switch. DDS expects a friendly local network; Zenoh expects to be told where to connect, so the verdict flips the moment traffic crosses Wi-Fi or a firewall. Neither changes what happens inside one machine, where shared-memory middleware such as HORUS is the lever. This post is for a ROS 2 team deciding whether a transport change is worth a week of their time.
The symptom that sends people looking is always the same. The robot works on the bench, and in the building it comes up half-connected. You list the topics and get eleven, restart something, and get fourteen. A colleague's laptop sees nodes yours cannot. The camera feed arrives steadily and then in bursts, and nobody changed anything. Somebody blames the access point, somebody else the firewall, and a third person quietly disables both and it makes no difference.
Then the release notes go around. There is another middleware option now, one that people say fixes discovery, and the team splits. Half want to try it this week. The other half remember the last time somebody changed a layer nobody understood, and how that cost a fortnight and produced a different set of mysteries. Neither half can say what the new option changes, because everything written about it is a specification or a benchmark, and neither answers the only question that matters: will the robot behave differently on Monday, and what does finding out cost?
Should I move my ROS 2 robot from DDS to Zenoh?
Move if any part of your graph crosses a network you do not administer, and stay where you are if it does not. That single test settles it for most teams, and it needs no understanding of either design. If every machine is plugged into the same switch, or the whole graph lives on one computer, DDS is operating in the environment it was designed for, and swapping the layer buys you unfamiliar configuration files and unfamiliar error messages in exchange for nothing you will feel. If any line in your system crosses Wi-Fi, crosses a building, sits behind a home router, or reaches a machine in a data centre, the picture inverts. That is the precise situation where a design that assumes it can shout on the local network and be heard quietly stops working, and where a design that connects to a known address was built for the problem you have. The decision is about whether your network matches the assumptions, and a network you do not run rarely matches the older set.
What does the middleware layer under ROS 2 actually do?
The middleware layer finds the other side, agrees what a message looks like, and moves the bytes, and nothing in your node code can tell which implementation is doing it. When you write a publisher, you name a topic and a type. Something underneath has to work out which processes on which machines care about that name, agree with each of them on the message layout, turn your object into bytes, get those bytes across whatever sits between the two machines, and decide what to do about the ones that do not arrive. ROS 2 keeps that layer swappable behind an interface on purpose, which is why this comparison is possible at all. Two robots running byte-identical node code can behave completely differently under load because the layer nobody looks at made different choices about discovery, queueing and retries. That is also why the layer is the last place anyone looks when something goes wrong: it is invisible while it works, and it has no representation in the code you wrote.
What are my real transport options under ROS 2 today?
There are about seven realistic options, and most robots that work well use two of them together. You can stay on the default DDS, which is right whenever your machines share a switch. You can stay on DDS but stop relying on automatic discovery, giving it a fixed address list or a discovery server, which resolves a surprising share of Wi-Fi trouble with no migration. You can move ROS 2 onto Zenoh, keeping every node, topic and message type and changing only what sits underneath. You can leave the graph alone and put a bridge at the edge, so one connection crosses the difficult link. You can move everything with a deadline onto the robot's own computer, which is what HORUS does for Rust, Python and C++ processes sharing the same shared-memory ring buffers, and what ROS 2 approximates by loading several nodes into one process. You can send telemetry over an ordinary web connection and stop pretending the dashboard is part of the robot. Or you can split a crowded graph into separate domains joined by a relay.
Which transport fits the robot I am actually running?
The transport that fits is decided by where your messages have to travel, not by which design reads better. Before you look at a row, draw the robot as boxes and lines and mark every line that leaves the machine it started on. Most teams find fewer such lines than expected, and find one carrying something nobody meant to put on a radio, like a camera feed going to a viewer somebody left open last Tuesday. That drawing decides more rows than any feature list does.
| Option | Who it is for | What it assumes you know | When to pick it | When not to |
|---|---|---|---|---|
| ROS 2 on the default DDS | Wired benches and lab setups | Ordinary ROS 2 usage | Every machine shares a switch | Traffic crosses Wi-Fi or a firewall |
| DDS with a discovery server or fixed peers | Teams staying on DDS but on radio | Peer lists and restart order | The node list is known in advance | Machines join and leave constantly |
| ROS 2 on Zenoh | Graphs that span networks or the internet | Running and supervising a router | Links are lossy, remote or firewalled | A wired switch already works fine |
| A bridge at the edge of the robot | Anyone streaming to a human operator | Which topics a person truly needs | The robot is autonomous and the link is for watching | A person closes the control loop by hand |
| HORUS on the robot's own computer | Builders mixing Rust, Python and C++ | Those languages, and life outside the ROS package set | Everything with a deadline shares one machine | You need ROS drivers or several machines |
| A plain web connection for telemetry | Dashboards and fleet monitoring | Basic web plumbing | Humans read the data, robots do not | Something on the robot waits for the reply |
| Separate domains joined by a relay | Crowded shared networks | Domain settings and topic hygiene | Unrelated robots share one network | The graph is small enough to leave alone |
Two rows is the usual honest answer: one for what happens on the robot, one for everything that leaves it.
What if my team is two people and nobody owns the network?
Prove the network is the problem before you migrate, because a two-person team cannot afford a fortnight spent on an unrelated layer. The test takes an afternoon and it separates the two cases cleanly. Run the entire system over a cable, with the radio off. If the graph comes up complete and stays complete, discovery over your wireless network is the culprit and a design that connects to known addresses genuinely helps you. If the same trouble follows you onto the cable, the transport is innocent, you have an ordinary bug wearing a costume, and switching layers will hide it for a week and then hand it back. Assuming the cable test points at the network, try the cheap fix on what you already run: a fixed peer list or a discovery server is an afternoon, is reversible, and resolves a good share of these cases without anyone learning a new tool. Migrate when that fails, or when the robot has to reach something outside the building, which is the case with no good answer on the older design.
What if my robot is a battery-powered machine on factory Wi-Fi?
That is the case where a transport change pays off most, and where an architecture change pays off more. A moving machine on a shared industrial network has every problem at once: the radio hands off between access points as the robot drives, the network is run by somebody in another department who will not enable anything for you, and the traffic that lets nodes find each other is the kind most networks discard first. Connecting to a known router instead of shouting into the air fixes the finding-each-other half, turning an intermittent nightmare into a machine that starts the same way every morning. What no transport fixes is the second half: a radio between two parts of a control loop will drop the biggest messages first, at the worst moment, and no amount of configuration changes that. So take the better transport for the link and simultaneously move anything with a deadline onto the robot itself. Those two fixes get confused constantly because they sit next to each other, and only the second one makes the robot stop caring whether the link is up.
What if I have to ship or demo within a month?
Do not change the transport with a deadline in front of you. A transport swap changes the layer everything else stands on, and the failures surface in the least convenient places: a service call that hangs only under load, a node that connects on the bench and not in the venue, a tool that used to work and now prints nothing. A month is enough time to make that change and not enough time to find out what it broke. Do the reversible things instead. Give discovery an explicit list of addresses so start-up stops depending on the venue's network. Move everything a human is not looking at onto the robot, so the wireless link carries a video feed and a few commands and nothing else. Make the robot behave sensibly when messages stop, which for a demo means slowing and stopping rather than continuing on the last thing it heard. Then schedule the migration for the week after, when a surprise costs you a day instead of the room. Demo halls are the worst radio environments most robots ever see.
What if nobody on the team has configured a middleware layer before?
Then start with the change that is easiest to undo, and read the failure before you read the documentation. Teams new to this layer usually make the same two mistakes. The first is changing several things at once, so when the system improves nobody knows which change did it and the knowledge does not survive the week. The second is treating the layer as a magic setting, applying a configuration file someone posted in a forum, and inheriting a set of behaviours nobody on the team can explain when they later cause trouble. The way in is narrower than it looks. Learn what discovery is and how to watch it happen, learn what queue depth does when a subscriber falls behind, and learn what your system does when a publisher disappears mid-run. Those three concepts explain most of what you will meet, and they transfer to every layer in this comparison, including a plain-English account of what middleware does if the vocabulary is still new.
What breaks first when the transport is a bad fit for the network?
Discovery breaks first, and it breaks in the most confusing way possible: partially. You do not get a clean failure with an error message. You get a graph that is complete on one machine and incomplete on another, a node that appears after a restart and vanishes after the next, and a tool that lists topics nobody is publishing because the announcement arrived and the data never did. The second thing to break is the largest message you send, usually images, because a radio drops big things before small things and a queue somewhere silently discards the oldest frame to make room. The third is start-up order, when a program that needed a service comes up before the service is visible and gives up. None of these look like a network problem to the person debugging, which is why teams spend weeks in their own code first. Why ROS 2 breaks on ordinary Wi-Fi is the same story told from the network's side.
What do I give up by moving to Zenoh?
You give up a decentralised system with nothing to run, and you take on a process to supervise. That is the central trade and everything else follows from it. The older design has no component in the middle: participants announce themselves, everyone hears everyone, and there is nothing that can fail on its own. The newer arrangement normally puts a router in the path, and a router is a thing that must be started, watched, restarted when it dies, and kept running through an update. On a robot that already runs supervised services, that is ordinary. On a robot with nothing else like it, that is a new category of thing to get wrong at midnight. You also give up familiarity, which is worth more than people admit: your team knows what the current failures look like, and after a migration every message is unfamiliar for a while. And you give up some of the ecosystem's default assumptions, since tutorials, forum answers and vendor instructions still assume the older layer.
When is ROS 2 the better choice?
ROS 2 on either transport beats leaving the framework whenever you want software other people already wrote or your robot spans more than one computer. This matters here because the transport argument often gets used as evidence that the whole framework is the problem, and that conclusion does not follow. If you need a mobile base to cross a building without hitting anyone, the navigation stack is a decade of work you are not going to reproduce. If you need collision-aware arm motion, that is solved and hard. If your sensors ship wrappers, your team is hiring, or you are reproducing published research, the framework is carrying most of your project. In all of those cases HORUS is not the answer, because a single-machine shared-memory layer supplies none of that catalogue and never claimed to. The right reading of a transport problem is narrow: one layer of your system does not match your network, and one layer is what you should change. There is a fuller version of this argument in whether ROS 2 is still the default choice.
Will switching to Zenoh make my robot feel more responsive?
Partly, but not the way you think. What improves is start-up and recovery: the graph assembles when you turn the robot on rather than when the network feels cooperative, a machine that drops off comes back without a restart ritual, and remote connections stop depending on somebody's access point settings. That genuinely feels better, and it is what most teams are chasing when they say responsive. What does not change is the path that matters most for motion, which is what happens between two programs on the same computer. On one machine, the cost is copying and converting a message so another process can read it, and that cost is the same whichever transport is configured above it, because it is a property of crossing a process boundary rather than a property of the network. So if your complaint is that the robot connects unpredictably, this change addresses it. If your complaint is that the loop hiccups when the camera pipeline gets busy, this change addresses nothing, and zero-copy messaging is the topic you actually want.
Is DDS being retired from ROS 2?
No, and here is why. DDS remains the default in current distributions, several implementations are actively maintained by separate organisations, and an enormous amount of shipping industrial equipment depends on it, including machines that were certified with a specific implementation named in the paperwork. None of that unwinds because a second option exists. What actually happened is narrower and more useful: the project accepted that the original bet, a decentralised design assuming a cooperative local network, was a poor fit for the networks a lot of robots now live on, and made room for an alternative rather than replacing the foundation. Teams choosing today should expect both to be supported for years and should pick on operational fit rather than on which one looks newer in a release note. If you want the longer view of whether the older layer still earns its place, the honest assessment of DDS covers the ground this section only summarises.
How do I decide which transport to run?
Decide with three tests, run in order, each of which is cheap enough to do this week. First, run the whole system over a cable with the radio off: if everything behaves, your problem is discovery over the network and a transport change is on the table. Second, count the lines that leave the machine they started on and look at what each carries: if the answer is a camera feed to a viewer nobody watches, you have an architecture problem in a transport costume. Third, name the one thing that must never be late, such as the wheels stopping before the robot reaches the edge of the dock, and find which computer makes that decision. If it is made on the robot, no transport choice touches it.
Take the line that matches you:
- If every machine shares a switch -> stay on DDS, because a swap buys new error messages and nothing you can feel.
- If nodes go missing over Wi-Fi -> try fixed peers or a discovery server first, because that is an afternoon and it is reversible.
- If the robot must reach a machine in another building -> Zenoh, because reaching through a router is the problem it was built for.
- If a person watches a feed and clicks buttons -> a bridge at the edge, because only one connection should cross the radio.
- If the thing that must not be late lives on one computer -> shared memory on that computer, because no transport choice touches that path.
The HORUS Fit Framework is the checklist behind those lines, and none of its five axes is a number: ecosystem size, setup effort, team size fit, deployment target, and licence. Score every option you are weighing on all five, and the axis you cannot compromise on decides it.
Pick the transport that matches your network, and solve the on-robot half separately. For that half, where Rust, Python and C++ processes share the same ring buffers on one computer and nothing is serialised between them, HORUS is open source under Apache-2.0. Star it so it is in your list when you start building.