Spawning a Custom URDF Model in Gazebo with the ROS 2 spawn_entity Service
Learn how to load a custom URDF robot into Gazebo Classic using the ROS 2 /spawn_entity service, including prerequisites, the exact service call, verification steps, and recovery options for common mesh and naming failures.
13 May 2026, 22:30 UTC

What You Are Trying to Achieve
You have a URDF (Unified Robot Description Format) file that describes your robot and want to see it inside Gazebo so you can test ROS 2 control logic before touching hardware. The direct approach is to start an empty Gazebo world and inject the model through the ROS 2 /spawn_entity service. The two most common blockers are incorrect mesh file paths and duplicate model names, so checking those before you call the service will save time.
Before You Start: Required Tools and Files
This guide assumes you are using Gazebo Classic 11 paired with ROS 2 Foxy or Humble. Newer Gazebo releases (for example, Fortress or later) use different package names and service interfaces, so the commands below may not apply without modification.
You need:
- A sourced ROS 2 workspace (
source /opt/ros/<distro>/setup.bash) in every terminal you use. Run these commands in a standard terminal; no elevated privileges are required. - A syntactically valid URDF file. If your source file is a xacro macro, expand it first.
- All mesh files referenced by the URDF (typically STL or DAE formats) present at the paths declared in the file.
- The
gazebo_rospackage, which provides the/spawn_entityservice.
Step 1: Launch an Empty Gazebo World
Open a terminal, source your ROS 2 setup, and start the simulator:
source /opt/ros/humble/setup.bash
ros2 launch gazebo_ros empty_world.launch.py
Keep this terminal running. Gazebo must stay alive while you spawn models; closing it destroys the simulation session.
Step 2: Call the /spawn_entity Service
Open a second terminal and source the workspace again. The service accepts a gazebo_msgs/SpawnEntity request with four fields: name, xml, robot_namespace, and initial_pose.
ros2 service call /spawn_entity gazebo_msgs/SpawnEntity "{name: 'my_robot', xml: '<urdf_xml_string>', robot_namespace: '', initial_pose: {position: {x: 0, y: 0, z: 0}, orientation: {x: 0, y: 0, z: 0, w: 1}}}"
Replace <urdf_xml_string> with the actual contents of your URDF file. Passing a long XML document inline is fragile; for anything beyond a trivial model, read the file into a shell variable or use a small Python script that calls the service programmatically.
Key field notes:
name: Must be unique within the running simulation. Reusing a name that already exists will cause the spawn to fail or overwrite the existing model.robot_namespace: Leave empty ('') unless you are deliberately running multiple robots under separate namespaces.initial_pose: The position and orientation where the model appears. A zero position with an identity orientation (w: 1) places the model at the world origin, upright.
Step 3: Confirm the Model Loaded
After the service call returns, verify the result in two ways.
First, check the simulation state topic:
ros2 topic echo /gazebo/model_states
Look for your model name in the name array. Its pose entry should match the initial_pose you supplied.
Second, look at the Gazebo GUI. The model should be visible in the viewport. Right-click it to confirm that its links and visual elements are present and selectable.
If the service call printed an error instead of a success response, do not proceed to control testing until the underlying issue is resolved.
If the Spawn Fails: Recovery Steps
Validate the URDF
Use the check_urdf tool to catch parsing errors before Gazebo sees the file:
check_urdf /path/to/robot.urdf
If you are starting from xacro, expand it first:
xacro /path/to/robot.urdf.xacro > /tmp/robot.urdf
check_urdf /tmp/robot.urdf
Check mesh paths
Gazebo resolves mesh paths relative to the URDF file location or via package:// URIs. If a mesh cannot be found, the model may spawn without visuals or fail entirely. Confirm that every <mesh filename="..."/> tag points to an accessible file.
Resolve naming conflicts
If you previously spawned a model with the same name, either delete it from the GUI or choose a new name value.
Restart the simulator
If Gazebo is in an inconsistent state, stop the launch process and start a fresh empty world before trying again.
Use the GUI as a fallback
If the service call is impractical for a one-off test, use the Gazebo Insert → Model tab to load the URDF directly. This bypasses ROS 2 messaging but confirms whether the file itself is valid.
Known Limitations
- Version drift. Gazebo Classic and the newer Gazebo (formerly Ignition) simulation stacks are not interchangeable. The
/spawn_entityservice described here belongs to the Classic +gazebo_rosintegration. If you migrate to a newer Gazebo, expect different package names, launch files, and service types. - URDF is not SDF. Gazebo parses URDF, but some simulator-specific features (certain sensor plugins, friction models, or world-level logic) require SDF or Gazebo-specific XML tags embedded in the URDF.
- ROS 2 control is separate. Spawning the model only places it in the world. Making joints move through ROS 2 topics requires additional configuration, typically via
gazebo_ros2_controland a controller configuration file. - Large meshes. High-poly meshes slow down both the spawn call and the rendering loop. Decimate meshes before referencing them if load times are unacceptable.
A Practical Way to Check the Result
Once the model appears in /gazebo/model_states and the GUI, publish a harmless test message to a relevant topic—such as a joint state publisher or a small velocity command—and observe whether the model responds. A visible reaction confirms that the model is not only present in the world but also connected to the ROS 2 communication network.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.