ROS 2 QoS: fix a topic that delivers no messages

Reproduce a ROS 2 reliability mismatch, inspect the publisher and subscriber profiles, and restore delivery. Run the checked Jazzy example in one isolated container.

By 9 min read

What you will learn

  • Reproduce a reliability mismatch on a topic with discovered endpoints.
  • Inspect the offered and requested QoS profiles with the ROS 2 command line.
  • Restore delivery by changing the subscriber's reliability requirement.
  • Distinguish compatibility from queue depth, durability, and data freshness.

Before you start

A ROS 2 topic can appear in the graph while incompatible QoS settings prevent message delivery. A best-effort publisher cannot satisfy a subscriber that requires reliable delivery. Changing that subscriber's requirement to best effort makes this pair compatible.

Here you will run a working pair, change one setting to break delivery, inspect the endpoints, and repair the mismatch. The example uses ROS 2 Jazzy and Fast DDS in one isolated Ubuntu container. You need a terminal and Docker; no robot or sensor data is required.

Why a visible topic can stay quiet

Quality of service, or QoS, describes communication policies. A publisher offers a profile, and a subscriber requests one. ROS 2 checks whether those profiles can work together.

The Jazzy QoS reference defines the following reliability compatibility. Other policies that affect compatibility must agree too.

Offered versus requested

Reliability compatibility has a direction

A publisher offers a level of reliability. A subscriber requests the minimum it will accept.

Publisher offersSubscriber requestsCan match?
Best effortBest effortYes
Best effortReliableNo
ReliableBest effortYes
ReliableReliableYes
This table shows reliability compatibility with all other compatibility policies held fixed and compatible. A best-effort publisher can send samples to a best-effort subscriber, but cannot satisfy a reliable subscriber. A match permits communication; it does not prove that an application has received a sample.

The distinction is directional. A reliable publisher can serve a best-effort subscriber. A best-effort publisher cannot meet a reliable subscriber's stronger requirement.

If publishers and subscribers are new to you, start with ROS 2 topics, services, and actions. This tutorial follows one topic through a delivery failure.

Start the tested ROS 2 environment

Validation on October 12, 2026 used Ubuntu 24.04.5 LTS, ROS 2 Jazzy, and rmw_fastrtps_cpp, the ROS middleware interface for Fast DDS. The test container used ARM64 on Docker Desktop for macOS. Linux and other architectures can produce different timing and identifiers; this run does not establish their results.

The installed package versions were ros2topic 0.32.12, rclpy 7.1.12, rmw_fastrtps_cpp 8.4.4, and Fast DDS (fastrtps) 2.14.6. The command pins the tested image digest; the environment record includes full package versions.

In a host terminal, start the container:

docker run --detach --rm --name ros2-qos-demo \
  --network none \
  --env ROS_DOMAIN_ID=71 \
  --env RMW_IMPLEMENTATION=rmw_fastrtps_cpp \
  ros:jazzy-ros-base@sha256:066420e07f60aa18262f2479981def87ebcfcec42eefb0c0c57c4a46098348ca \
  sleep infinity

Docker pulls the image if needed. Keep three terminal windows available. In each window, enter the same container and source ROS:

docker exec -it ros2-qos-demo bash
source /opt/ros/jazzy/setup.bash

All ROS commands below run inside that container. Its processes share one network namespace, and --network none isolates this exercise from other machines. This setup tests local QoS compatibility; it does not test communication across containers or computers.

If Docker reports that the container name already exists, reconnect to your existing tutorial container or choose another name consistently.

Receive one message first

In terminal A, start a publisher and leave it running:

ros2 topic pub /qos_demo std_msgs/msg/String "{data: 'hello qos'}" \
  --node-name qos_demo_pub --rate 2 --wait-matching-subscriptions 0 \
  --qos-reliability best_effort --qos-durability volatile \
  --qos-history keep_last --qos-depth 10

It publishes at a requested rate of 2 Hz. The explicit wait setting lets it publish even when no subscriber matches. The message type is std_msgs/msg/String; the payload is a fixed string so a changed reading cannot confuse the comparison.

In terminal B, request best-effort delivery:

ros2 topic echo /qos_demo std_msgs/msg/String --once --timeout 10 --no-daemon \
  --qos-reliability best_effort --qos-durability volatile \
  --qos-history keep_last --qos-depth 10

The subscriber prints one sample and exits:

data: hello qos
---

--once means stop after receiving one sample. --timeout 10 bounds the wait if the setup fails. Confirm the payload in the output; a process exit alone does not prove reception. Resolve a missing baseline before testing the mismatch.

Change one reliability requirement

Keep the publisher in terminal A running. In terminal B, repeat the echo with reliable as its only changed policy. Leave this command running while you inspect terminal C:

ros2 topic echo /qos_demo std_msgs/msg/String --once --no-daemon \
  --qos-reliability reliable --qos-durability volatile \
  --qos-history keep_last --qos-depth 10

The checked Fast DDS run logged an incompatible-QoS warning ending with:

Last incompatible policy: RELIABILITY

No data: hello qos sample arrived. The publisher still emitted messages. Because this command has no timeout, it waits until you interrupt it or a compatible publisher supplies a sample.

Set the policies explicitly for this exercise. Jazzy's echo implementation can select compatible reliability and durability from discovered publishers when you omit overrides. A plain ros2 topic echo /qos_demo may therefore receive data and skip the failure you are trying to study.

Inspect both endpoints while the failure is active

In terminal C, check the topic and its endpoints:

ros2 topic list -t --no-daemon --spin-time 2
ros2 topic info /qos_demo --verbose --no-daemon --spin-time 2

The first command includes:

/qos_demo [std_msgs/msg/String]

The verbose output contains a publisher offering BEST_EFFORT and a subscription requesting RELIABLE. These are selected fields from the captured output:

Publisher count: 1
Node name: qos_demo_pub
Endpoint type: PUBLISHER
QoS profile:
  Reliability: BEST_EFFORT
  History (Depth): UNKNOWN
  Durability: VOLATILE

Subscription count: 1
Endpoint type: SUBSCRIPTION
QoS profile:
  Reliability: RELIABLE
  History (Depth): UNKNOWN
  Durability: VOLATILE

Endpoint counts tell you what the graph discovered. They do not count successful message exchanges. Here the graph sees both endpoints even though they cannot communicate with each other.

The test reported History (Depth): UNKNOWN despite the explicit history and depth flags. Preserve that observation when reading this middleware's endpoint report; it does not say the command used an unknown queue setting. The reported reliability fields and warning identify this failure. The captured run includes the complete endpoint output, including generated identifiers that vary between runs.

Restore delivery and check the result

Press Ctrl+C in terminal B to stop the incompatible subscriber. Run the baseline echo again:

ros2 topic echo /qos_demo std_msgs/msg/String --once --timeout 10 --no-daemon \
  --qos-reliability best_effort --qos-durability volatile \
  --qos-history keep_last --qos-depth 10

The checked run received data: hello qos again. The publisher, topic, type, and other policies stayed the same. That controlled change isolates the incompatible reliability requirement.

Choose this repair when the application can accept best-effort samples. If the application requires reliable delivery, configure a publisher that offers it and check the resulting behavior. Changing every subscriber to best effort would hide that application requirement.

Separate reliability from the other policies

The explicit profile holds several choices fixed:

  • Reliability: best effort permits sample loss. Reliable delivery uses recovery behavior, subject to the rest of the system's limits.
  • History and depth: keep_last with depth 10 retains up to ten samples in a history. A larger queue cannot fix this reliability mismatch.
  • Durability: volatile supplies no retained history to late subscribers. A compatible transient-local setup addresses that separate need.

These settings do not establish a measured delivery rate, bounded latency, or a fresh physical reading. A controller must still examine timestamps and its own processing delays. The wheel-odometry lesson shows what encoder readings contribute to a pose estimate; coordinate frames explain how to interpret that estimate's coordinates.

Check other causes of an empty echo

A quiet topic is a symptom with several possible causes. Use the evidence to narrow it:

ObservationNext check
No publisher in the verbose reportConfirm the publishing process, topic name, and namespace.
Your terminals see different graphsCheck ROS environments, domain IDs, discovery, and networking. For this exercise, enter the same container.
Endpoints use different message typesCorrect the type or topic selection before changing QoS.
Profiles conflictIdentify the incompatible policy and change the endpoint whose requirement permits it.
Compatible profiles, still no sampleCheck whether the publisher actually emits, whether it stopped after one sample, and whether a late subscriber needs retained history.
Samples arrive but the robot behaves badlyCheck timestamp age, units, frames, sensor quality, and the downstream computation.

Warnings vary with middleware and version. Their absence does not prove compatibility. In this example, the working baseline, endpoint inspection, explicit warning, and successful repair support the diagnosis together.

Run the downloadable verification script

Download verify.sh. It runs the same baseline, inspects a failed subscriber during a 15-second observation, checks for the reliability warning and absence of payloads, then checks the repair. It exits with a failure if an expected observation is missing.

Use a fresh tutorial container, or stop terminal A's publisher and terminal B's subscriber first with Ctrl+C. Extra endpoints would invalidate this one-publisher, one-subscriber check. From the host folder containing the download, run:

docker exec -i ros2-qos-demo bash < verify.sh

The script reports these checkpoints, with the endpoint report and warning between them:

BASELINE: best_effort -> best_effort received hello qos
MISMATCH: topic and both endpoints discovered; RELIABILITY warning; no sample in 15 seconds
REPAIR: best_effort -> best_effort received hello qos
PASS: baseline, incompatible reliability, endpoint inspection, and repair

The timeout bounds the experiment; silence alone would not establish its cause. The script checks the observed profiles and warning as well. These results concern local delivery compatibility, with no packet-loss or latency benchmark.

When finished, exit the container shells. In a host terminal, stop your tutorial container:

docker stop ros2-qos-demo

Docker removes it because the launch used --rm; the downloaded image remains available.

Once delivery works, use ROS 2 recording and replay to preserve a small stream and check it again. Its clock experiment separates successful delivery from the receiving node’s time source.

Try two diagnostic decisions

Exercise 1. A best-effort publisher and reliable subscriber both use depth 10. Would increasing both depths to 100 repair the mismatch? Name the setting that can repair this example.

Check the reliability repair

Increasing depth leaves reliability unchanged, so the pair remains incompatible. Request best effort at the subscriber if its application permits sample loss, or use a publisher that offers reliable delivery.

Exercise 2. The verbose report lists one publisher and one subscription. An echo prints nothing. What evidence would you gather before blaming QoS?

Check the diagnostic evidence

Compare the topic, type, and endpoint profiles, then check for a policy warning and actual publishing activity. Establish a compatible baseline and change one policy at a time. Endpoint counts alone establish discovery, so they cannot prove either successful delivery or a QoS fault.

Sources and further study

Next, connect odometry to a timestamped tf2 transform. That checked Jazzy example separates message receipt, frame connectivity, and measurement time. For the pose calculation itself, wheel odometry shows how encoder measurements and calibration errors affect the estimate.