ROS 1 to ROS 2 Bridge Guide 2026
Migrate gradually from ROS 1 (Noetic) to ROS 2 (Humble/Jazzy) using ros1_bridge: set up a dual-source environment, use dynamic_bridge for auto-discovery or parameter_bridge for explicit topic mapping, bridge /tf, and port custom message types.
Migration Strategy
Start by bridging stable ROS 1 nodes (sensor drivers, hardware interfaces) into ROS 2, while writing new features natively in ROS 2. Port the most critical nodes first, then remove the bridge when the system is fully migrated. Avoid keeping ros1_bridge in production long-term — it adds latency and complexity.
ros1_bridge — When and Why
ros1_bridge lets ROS 1 (Noetic) and ROS 2 (Humble/Jazzy) nodes talk to each other. It's the standard migration path: keep stable ROS 1 nodes running while you port others to ROS 2.
ros1_bridge Architecture
═══════════════════════════════════════════════════════
ROS 1 nodes ←→ ros1_bridge ←→ ROS 2 nodes
(XMLRPC/TCPROS) (DDS/FastDDS)
What it bridges:
Topics — pub/sub with automatic type mapping
Services — request/response cross-version
Actions — NOT directly; use topic bridge for actionlib topics
Requirements:
1. ROS 1 (Noetic) installed alongside ROS 2 (Humble/Jazzy)
2. Both environments must be sourced in the SAME shell
3. Message types must have equivalent definitions in both distros
4. ROS 1 roscore must be running
Installation:
sudo apt install ros-humble-ros1-bridge
Source order (critical):
source /opt/ros/noetic/setup.bash # ROS 1 FIRST
source /opt/ros/humble/setup.bash # ROS 2 SECOND
# (ROS 2 setup must come after ROS 1)
Limitations:
- Only bridges topics/services with matching type pairs
- Custom message types need source on BOTH sides and recompilation
- Not available in containers without dual ROS install
- Adds latency (~1-5ms per hop)
- Not a long-term solution — plan to port fully to ROS 2⚡ For new robots, avoid ros1_bridge entirely and write directly in ROS 2. Use the bridge only for large existing codebases where full porting would take months. Prioritize porting the most critical nodes first.
Environment Setup — Dual-Source Shell
Both ROS 1 and ROS 2 must be sourced in the same terminal session. The roscore must be running before the bridge starts.
# ── Terminal 1: source both, start roscore ───────────────────
source /opt/ros/noetic/setup.bash
source /opt/ros/humble/setup.bash
# Verify both are sourced
echo $ROS_DISTRO # should show: humble (last sourced wins)
roscore & # start ROS 1 master in background
# Alternatively (systemd or separate terminal):
# ROS_MASTER_URI=http://localhost:11311 roscore &
# ── Verify roscore is up ─────────────────────────────────────
rosnode list # should show /rosout (ROS 1 nodes)
ros2 node list # should show nothing yet
# ── Check bridge package is installed ────────────────────────
ros2 pkg list | grep ros1_bridge
# → ros1_bridge
# ── Environment variables ─────────────────────────────────────
# Tells bridge where to find the ROS 1 master
export ROS_MASTER_URI=http://localhost:11311
export ROS_HOSTNAME=localhost # for multi-machine setupsdynamic_bridge — Auto-Bridge All Topics
dynamic_bridge monitors ROS 1 and ROS 2 topic registrations and automatically creates bridges for topics where both sides have active publishers and subscribers.
# ── Terminal 1: source + roscore ─────────────────────────────
source /opt/ros/noetic/setup.bash
source /opt/ros/humble/setup.bash
roscore &
# ── Terminal 2: start dynamic_bridge ─────────────────────────
source /opt/ros/noetic/setup.bash
source /opt/ros/humble/setup.bash
ros2 run ros1_bridge dynamic_bridge
# dynamic_bridge output:
# [INFO] [ros1_bridge]: Waiting for ROS 1 master...
# [INFO] [ros1_bridge]: ROS 1 master at http://localhost:11311
# created 1 bridges for topics [/scan] ...
# created 1 bridges for topics [/cmd_vel] ...
# ── Terminal 3: ROS 1 publisher ───────────────────────────────
source /opt/ros/noetic/setup.bash
rostopic pub /chatter std_msgs/String "data: 'Hello from ROS 1'" -r 1
# ── Terminal 4: ROS 2 subscriber ─────────────────────────────
source /opt/ros/humble/setup.bash
ros2 topic echo /chatter # receives ROS 1 messages!
# ── Bridge direction ──────────────────────────────────────────
# By default, bridges BOTH directions:
# ROS 1 pub → ROS 2 sub (and vice versa)
# Bridge only activates when BOTH ends have a pub and a sub
# Bridge only ROS 1 → ROS 2:
ros2 run ros1_bridge dynamic_bridge --bridge-all-1to2-topics⚡ dynamic_bridge only creates a bridge when there's at least one publisher AND one subscriber on both sides. If you publish from ROS 1 but have no ROS 2 subscriber yet, no bridge is created. This is by design to avoid unnecessary traffic.
static_bridge — Explicit Topic/Service Mapping
static_bridge lets you explicitly specify which topics and services to bridge, with their types. This is more predictable and avoids the auto-discovery overhead of dynamic_bridge.
# static_bridge with explicit topic and type mapping
# Bridge a specific topic (ROS 1 → ROS 2)
ros2 run ros1_bridge static_bridge --ros1-topic /scan --ros2-topic /scan --ros1-type sensor_msgs/LaserScan --ros2-type sensor_msgs/msg/LaserScan
# Bridge multiple topics (using parameter YAML)
# bridge_params.yaml:
# ---
# topics:
# - topic: /scan
# type: sensor_msgs/msg/LaserScan
# queue_size: 10
# - topic: /odom
# type: nav_msgs/msg/Odometry
# queue_size: 10
# - topic: /cmd_vel
# type: geometry_msgs/msg/Twist
# queue_size: 1
# - topic: /tf
# type: tf2_msgs/msg/TFMessage
# queue_size: 10
# services:
# - service: /add_two_ints
# type: example_interfaces/srv/AddTwoInts
# Run with YAML config
ros2 run ros1_bridge parameter_bridge /scan@sensor_msgs/msg/LaserScan[sensor_msgs/LaserScan /odom@nav_msgs/msg/Odometry[nav_msgs/Odometry /cmd_vel@geometry_msgs/msg/Twist]geometry_msgs/Twist /tf@tf2_msgs/msg/TFMessage[tf2_msgs/TFMessage
# Syntax: topic@ros2_type[ros1_type (ROS 1 → ROS 2)
# topic@ros2_type]ros1_type (ROS 2 → ROS 1)
# topic@ros2_type[ros1_type] (bidirectional)Custom Message Type Bridging
Bridging custom message types requires the package source on both the ROS 1 and ROS 2 sides, plus a bridge mapping package.
# For custom messages, you need source packages on both sides.
# Example: my_custom_msgs package
# ── Step 1: Create the ROS 1 package ─────────────────────────
# ~/ros1_ws/src/my_custom_msgs/msg/RobotStatus.msg:
# float32 battery_level
# bool is_moving
# string error_message
# Build in ROS 1 workspace
cd ~/ros1_ws && catkin_make
# ── Step 2: Create the ROS 2 package ─────────────────────────
# ~/ros2_ws/src/my_custom_msgs/msg/RobotStatus.msg (identical)
# float32 battery_level
# bool is_moving
# string error_message
# Build in ROS 2 workspace
cd ~/ros2_ws && colcon build
# ── Step 3: Rebuild ros1_bridge with both workspaces ─────────
source /opt/ros/noetic/setup.bash
source ~/ros1_ws/devel/setup.bash # ROS 1 custom msgs
source /opt/ros/humble/setup.bash
source ~/ros2_ws/install/setup.bash # ROS 2 custom msgs
colcon build --packages-select ros1_bridge --cmake-force-configure
# ros1_bridge auto-detects the paired message types and generates bridges
# ── Verify custom type bridge was generated ──────────────────
ros2 run ros1_bridge ros1_bridge_generate_factories --print
# Should list: my_custom_msgs/msg/RobotStatus⚡ ros1_bridge finds matching type pairs at compile time — if you add a custom message after building the bridge, you must rebuild ros1_bridge from source. This is the most common pain point with custom types.
Troubleshooting Common Bridge Issues
The most frequent ros1_bridge problems: no bridge created, type mismatch, QoS incompatibility, and performance issues.
# ── Issue 1: No bridge created for my topic ──────────────────
# Cause: dynamic_bridge requires pub AND sub on both sides
# Fix: start a subscriber on the receiving side first
# Debug: list available bridge pairs
ros2 run ros1_bridge dynamic_bridge --print-pairs
# Shows all type pairs it knows how to bridge
# ── Issue 2: "No factory for type" error ─────────────────────
# Cause: custom message type not in bridge's factory
# Fix: rebuild ros1_bridge with both workspaces sourced
# (see custom messages section above)
# ── Issue 3: QoS incompatibility ─────────────────────────────
# ros1_bridge publishes with RELIABLE QoS by default
# If your ROS 2 node uses BEST_EFFORT, add QoS override:
ros2 topic echo /scan --qos-reliability best_effort
# ── Issue 4: TF transforms not bridging ──────────────────────
# /tf and /tf_static need explicit bridges in parameter_bridge
ros2 run ros1_bridge parameter_bridge /tf@tf2_msgs/msg/TFMessage[tf2_msgs/TFMessage /tf_static@tf2_msgs/msg/TFMessage[tf2_msgs/TFMessage
# ── Issue 5: High latency or dropped messages ─────────────────
# Cause: bridge is single-threaded by default
# Fix: use separate bridge instances per topic for parallelism
# Or: reduce queue_size for low-latency topics like /cmd_vel
# ── Verify bridge is working ──────────────────────────────────
# ROS 2 side: check message rate
ros2 topic hz /scan # should match ROS 1 publish rate
# ROS 1 side:
rostopic hz /scan # confirm ROS 1 publisher rateQuick Reference
| Command / Concept | Details |
|---|---|
| Install | sudo apt install ros-humble-ros1-bridge |
| Source order | source noetic/setup.bash THEN humble/setup.bash (ROS 2 last) |
| dynamic_bridge | ros2 run ros1_bridge dynamic_bridge (auto, needs pub+sub on both) |
| parameter_bridge | ros2 run ros1_bridge parameter_bridge /topic@ros2_type[ros1_type |
| Direction syntax | [ros1_type = ROS1→ROS2, ]ros1_type = ROS2→ROS1, both = bidirectional |
| Bridge /tf | /tf@tf2_msgs/msg/TFMessage[tf2_msgs/TFMessage (explicit) |
| Custom msgs | Rebuild ros1_bridge from source with both workspaces sourced |
| List bridge pairs | ros2 run ros1_bridge dynamic_bridge --print-pairs |
| No bridge created | dynamic_bridge needs both publisher AND subscriber active |
| Verify | ros2 topic hz /topic — should match ROS 1 publish rate |