Migrate Ign Gazebo to Gz Sim
Migrate Ign Gazebo to Gz Sim
(Specifically, migrating from ROS2 Humble to ROS2 Jazzy and later versions. Even projects using only Humble are recommended to review this, as many coding practices can affect future upgrades.)
In the ROS2 Humble era, the new version of Gazebo still frequently uses the Ignition / Ign Gazebo naming, for example the command is ign gazebo, the ROS package name is commonly ros_ign_*, and the plugin file name is often ignition-gazebo-xxx-system.
With ROS2 Jazzy, the official pairing is Gazebo Harmonic. At this point, the naming system has largely switched to Gazebo / Gz Sim — for example, the command becomes gz sim, the ROS package name changes to ros_gz_*, and the plugin file name changes to gz-sim-xxx-system.
So when migrating from Humble to Jazzy, the biggest compatibility issues are usually with Gazebo. The interface and functionality look similar, but many names in the code are not interchangeable, requiring migration from the Ignition-era naming to the Gz Sim / Gazebo Harmonic conventions.
It can be simply understood as: most of the migration is not about rewriting functionality, but rather migrating the naming system, plugin names, launch package names, SDF tags, and environment variables.
Official reference:
- Gazebo Harmonic Migration Notes: https://gazebosim.org/docs/harmonic/migration_from_ignition/
- Gazebo Fuel Model Reference Example: https://gazebosim.org/docs/harmonic/fuel_insert/
- Gazebo Sensors Example: https://gazebosim.org/docs/harmonic/sensors/
- ROS2 Gazebo Classic Migration Guide to New Gazebo: https://gazebosim.org/docs/harmonic/migrating_gazebo_classic_ros2_packages/
- SDFormat Sensor Specification: https://sdformat.org/spec
Before migrating, first search for the old syntax.
Run the following command in the project root directory:
rg -n -i "ignition|ignition-gazebo|libignition|ros_ign|ign_args|ign_version|fuel.ignitionrobotics|ignition_frame_id|<ignition-gui>|ign gazebo|ign topic|alwaysOn" src
Focus on checking these files:
*.sdf: world, model, GUI, plugins, and Fuel URL are usually found here.*.urdf.xacro/*.urdf: Robot plugins and sensor tags are usually found here.*.launch.py: Gazebo launch package, launch parameters, and bridged topics are usually located here.package.xml: Runtime dependencies are usually located here.CMakeLists.txt: The installation directory and build dependencies are usually located here.
It is recommended to add paired Chinese comments when making modifications, so that it will be easier to know where to start and where to end in the future.
<!-- Jazzy 迁移开始:说明这里为什么要改。 -->
<plugin filename="gz-sim-physics-system" name="gz::sim::systems::Physics"/>
<!-- Jazzy 迁移结束:说明这里已经改成什么写法。 -->
SDF file

According to the official migration guide, common naming usually replaces ignition / ign with gz.

The plugin should be renamed according to the new naming convention, for example:
ignition::gazebonamespace changed togz::simignition::changed togz::ignition-gazebo-XXX-systemchanged togz-sim-XXX-systemlibignition-gazebo-XXX-system.sochanged togz-sim-XXX-system
Common Replacement Table:
| Humble / Ignition syntax | Jazzy / Gazebo Harmonic notation | Example scenario |
|---|---|---|
ignition-gazebo-* | gz-sim-* | Plugin file name |
libignition-gazebo-*.so | gz-sim-* | Plugin file name |
ignition::gazebo::* | gz::sim::* | Plugin Namespace |
ros_ign_* | ros_gz_* | ROS-Gazebo package name |
ign_args | gz_args | launch parameters |
ign_version | gz_version | launch parameters |
IGN_GAZEBO_RESOURCE_PATH | GZ_SIM_RESOURCE_PATH | Model/Resource Path |
fuel.ignitionrobotics.org | fuel.gazebosim.org | Gazebo Fuel URL |
System plugin
Old writing method:
<plugin filename="libignition-gazebo-physics-system.so"
name="ignition::gazebo::systems::Physics"/>
Writing style for Jazzy / Gazebo Harmonic:
<plugin filename="gz-sim-physics-system"
name="gz::sim::systems::Physics"/>
Common System Plugins:
| old plugin | New plugin |
|---|---|
ignition-gazebo-physics-system | gz-sim-physics-system |
ignition-gazebo-sensors-system | gz-sim-sensors-system |
ignition-gazebo-scene-broadcaster-system | gz-sim-scene-broadcaster-system |
ignition-gazebo-user-commands-system | gz-sim-user-commands-system |
ignition-gazebo-contact-system | gz-sim-contact-system |
ignition-gazebo-diff-drive-system | gz-sim-diff-drive-system |
ignition-gazebo-joint-state-publisher-system | gz-sim-joint-state-publisher-system |
GUI tag
The old version of the Gazebo GUI configuration might be written as:
<ignition-gui>
<property type="string" key="state">docked</property>
</ignition-gui>
Change "Jazzy / Gazebo Harmonic" to:
<gz-gui>
<property type="string" key="state">docked</property>
</gz-gui>
You can check it with the following command:
rg -n "<ignition-gui>|</ignition-gui>" src
Gazebo sensors in URDF / Xacro
The sensor section is the most likely place to leave warnings when migrating to Jazzy.
always_on
Old writing method:
<alwaysOn>true</alwaysOn>
Jazzy / SDF Standard Format:
<always_on>true</always_on>
If not changed, Gazebo may prompt:
XML Element[alwaysOn], child of element[sensor], not defined in SDF.
ignition_frame_id / gz_frame_id and pose relative_to
The old way of writing might be:
<ignition_frame_id>laser</ignition_frame_id>
Some Gazebo ROS migration documents will mention:
<gz_frame_id>laser</gz_frame_id>
However, in the current ROS2 Jazzy / Gazebo Harmonic toolchain, converting URDF to SDF typically outputs SDF 1.11, and SDF 1.11 does not have a standard <gz_frame_id> element. Therefore, the following may appear at startup:
XML Element[gz_frame_id], child of element[sensor], not defined in SDF. Copying[gz_frame_id] as children of [sensor].
But honestly, this warning is just a warning — in reality, this tag does take effect and must be used. Please ignore those warnings.
Then you can also add the following pose relative_to tag, which is provided in the official example.
<pose relative_to="laser">0 0 0 0 0 0</pose>
Camera Example:
<sensor name="cam_link" type="camera">
<update_rate>10.0</update_rate>
<always_on>true</always_on>
<pose relative_to="camera">0 0 0 0 0 0</pose>
<gz_frame_id>camera</gz_frame_id>
<topic>/image_raw</topic>
<camera name="my_camera">
...
</camera>
</sensor>
Radar Example:
<sensor name="gpu_lidar" type="gpu_lidar">
<topic>scan</topic>
<update_rate>30</update_rate>
<always_on>true</always_on>
<visualize>true</visualize>
<pose relative_to="laser">0 0 0 0 0 0</pose>
<gz_frame_id>laser</gz_frame_id>
<lidar>
...
</lidar>
</sensor>
Note: pose relative_to and gz_frame_id are not the same concept.
pose relative_to: Indicates the pose of the sensor relative to which link/frame, addressing the question of "where the sensor is placed."gz_frame_id: Try specifying the frame ID in the sensor message to solve the problem of "what to write in the message header.frame_id".
It is recommended to use both pose relative_to and <gz_frame_id> simultaneously.


Launch file
Jazzy uses ros_gz_sim to launch Gazebo, instead of the old ros_ign_gazebo / ros_ign.
The old approach is often:
pkg_ros_ign_gazebo = get_package_share_directory("ros_ign_gazebo")
Jazzy recommendation:
pkg_ros_gz_sim = get_package_share_directory("ros_gz_sim")
Launch file syntax for starting Gazebo:
gz_sim = IncludeLaunchDescription(
PythonLaunchDescriptionSource(
os.path.join(pkg_ros_gz_sim, "launch", "gz_sim.launch.py")),
launch_arguments={
"gz_args": "-r " + world_file
}.items(),
)
Note: Parameter names should also be changed:
| Humble / Ignition | Jazzy / Gazebo Harmonic |
|---|---|
ign_args | gz_args |
ign_version | gz_version |
To spawn URDF / robot_description, Jazzy uses:
spawn = Node(
package="ros_gz_sim",
executable="create",
arguments=[
"-name", "mycar",
"-topic", "/robot_description",
],
output="screen",
)
ROS and Gazebo Message Bridging
Jazzy uses ros_gz_bridge:
bridge = Node(
package="ros_gz_bridge",
executable="parameter_bridge",
arguments=[
"/cmd_vel@geometry_msgs/msg/Twist@gz.msgs.Twist",
"/scan@sensor_msgs/msg/LaserScan@gz.msgs.LaserScan",
"/image_raw@sensor_msgs/msg/Image@gz.msgs.Image",
],
)
Checkpoint:
- ROS package name is
ros_gz_bridge - Gazebo message namespace uses
gz.msgs.* - If the topic is not bridged out, first run
gz topic -lto check the actual topic name on the Gazebo side, then modify the bridge parameters.
package.xml dependencies
If the launch file uses ros_gz_sim and ros_gz_bridge, package.xml must also declare a runtime dependency.
<exec_depend>ros_gz_sim</exec_depend>
<exec_depend>ros_gz_bridge</exec_depend>
<exec_depend>geometry_msgs</exec_depend>
<exec_depend>nav_msgs</exec_depend>
<exec_depend>tf2_msgs</exec_depend>
<exec_depend>sensor_msgs</exec_depend>
<exec_depend>rosgraph_msgs</exec_depend>
If RViz is also launched, add that as well.
<exec_depend>rviz2</exec_depend>
Fuel URL and model path
Old Fuel URL (the old link actually still works, as it redirects to the new link):
<uri>https://fuel.ignitionrobotics.org/1.0/openrobotics/models/Playground</uri>
New Fuel URL:
<uri>https://fuel.gazebosim.org/1.0/openrobotics/models/Playground</uri>
Do not blindly change the local model path. For example:
<uri>file:///home/xxx/ROS_WS/ign_models/bed</uri>
The ign_models here might just be a folder name you created yourself, not a Gazebo API name. As long as this directory actually exists, you can keep it.
If you want to change to a more general model path, you can set the following macro, changing it from IGN_GAZEBO_RESOURCE_PATH to GZ_SIM_RESOURCE_PATH:
export GZ_SIM_RESOURCE_PATH=/path/to/model_parent
Then write in the SDF:
<uri>model://bed</uri>
Command line migration
| Humble / Ignition commands | Jazzy / Gazebo Harmonic Commands |
|---|---|
ign gazebo world.sdf | gz sim world.sdf |
ign gazebo -r world.sdf | gz sim -r world.sdf |
ign topic -l | gz topic -l |
ign topic -e -t /scan | gz topic -e -t /scan |
ROS launch is still used:
ros2 launch 包名 launch文件.py
Verification sequence after migration
1. Check if XML / SDF is valid
xmllint --noout src/demo_gazebo_sim/world/house.sdf
xmllint --noout src/demo_gazebo_sim/world/visualize_lidar.sdf
xmllint --noout src/mycar_description/urdf/xacro/gazebo_sensor.urdf.xacro
2. Check if xacro can generate URDF
source /opt/ros/jazzy/setup.bash
xacro src/mycar_description/urdf/xacro/car.urdf.xacro -o /tmp/car_check.urdf
3. Check if there are still warnings when converting Gazebo to SDF
gz sdf -p /tmp/car_check.urdf
If there are still gz_frame_id or alwaysOn warnings, go back to the sensor section and continue making changes.
4. Building the Project
colcon build --symlink-install
source install/setup.bash
5. Launch Gazebo
ros2 launch demo_gazebo_sim gazebo_sim_world.launch.py
ros2 launch demo_gazebo_sim gazebo_sim_robot_world.launch.py
You can also run just the Gazebo server for a quick check:
gz sim -s -r -v 2 src/demo_gazebo_sim/world/house.sdf --iterations 1
Common Error Troubleshooting
Plugin not found
Check if it still says:
ignition-gazebo-*
libignition-gazebo-*.so
ignition::gazebo::systems::*
Jazzy should be changed to:
gz-sim-*-system
gz::sim::systems::*
GUI tag warning
Check if there are any remaining:
<ignition-gui>
Jazzy should be changed to:
<gz-gui>
Sensor warning
Check if there are any remaining:
<alwaysOn>
<ignition_frame_id>
Suggestion to change to:
<always_on>true</always_on>
<pose relative_to="laser">0 0 0 0 0 0</pose>
<gz_frame_id>laser</gz_frame_id>
or
<pose relative_to="camera">0 0 0 0 0 0</pose>
<gz_frame_id>camera</gz_frame_id>
robot_state_publisher KDL root link inertia warning
If the log shows:
The root link base_footprint has an inertia specified in the URDF, but KDL does not support a root link with an inertia.
The URDF root link includes <inertial>, which KDL does not support.
Changed the xacro but the log still shows the old content.
First, confirm that the old launch has been stopped:
pgrep -af "gazebo|gz sim|robot_state_publisher|parameter_bridge"
If the old robot_state_publisher is still present, ros_gz_sim create in the same ROS graph might retrieve the model from the old /robot_description.
After stopping the old process, rebuild, source, and start:
colcon build --symlink-install
source install/setup.bash
ros2 launch demo_gazebo_sim gazebo_sim_robot_world.launch.py
QStandardPaths / libEGL warning
If only this remains in the log:
QStandardPaths: runtime directory '/run/user/1000' is not owned by UID 0
libEGL warning: egl: failed to create dri2 screen
This is usually not a Gazebo migration error, but a warning caused by launching the GUI or graphics rendering environment as root. It is recommended to run ROS/Gazebo as a regular user, or check the graphics driver, Mesa, and EGL environment.
Finally, scan through the old references one more time.
Functional Old Reference Check:
rg -n "filename=['\"](lib)?ignition|name=['\"]ignition::gazebo|<ignition-gui>|</ignition-gui>|<ignition_frame_id>|</ignition_frame_id>|<gz_frame_id>|</gz_frame_id>|fuel\\.ignitionrobotics\\.org|ros_ign|ign_args|ign_version|alwaysOn" src
Official documentation reminder for false replacement check:
rg -n -i "gz-gazebo|gzition|an gz" src
If the old syntax is only found in Chinese comments or tutorials, but not in the functional code, it's generally been fully migrated.