URDF vs. SDF vs. MJCF: Pick the Right Robot Format in 10 Minutes

Compare URDF, SDF, and MJCF robot description formats. Know when to use each, how they differ, and which one fits your simulator.

Problem: Three Formats, One Robot, Zero Clarity

You're setting up a robot simulation and you've hit a wall: your robot model exists in URDF, but Gazebo wants SDF and your reinforcement learning pipeline runs on MuJoCo. Converting between them keeps breaking things.

You'll learn:

  • What each format is actually good at (and where it falls apart)
  • The key structural differences between URDF, SDF, and MJCF
  • Which format to start with based on your use case

Time: 10 min | Level: Intermediate


Why This Happens

These three formats evolved from different communities solving different problems. URDF came out of ROS for describing robot kinematics. SDF came from Gazebo to handle full simulation environments. MJCF was built by DeepMind specifically for contact-rich physics. Each format optimized for its own use case, and none of them agreed on conventions.

Common symptoms:

  • Your URDF loads in RViz but behaves wrong in Gazebo
  • MuJoCo ignores friction and damping values from your URDF conversion
  • Your articulated hand model loses constraints when you switch simulators

The Formats at a Glance

URDF: Universal Robot Description Format

URDF is XML. It describes a robot as a tree of link and joint elements. That tree structure is the key constraint — URDF cannot represent closed kinematic chains (loops).

<!-- Basic URDF structure -->
<robot name="my_arm">

  <link name="base_link">
    <visual>
      <geometry><box size="0.1 0.1 0.1"/></geometry>
    </visual>
    <inertial>
      <mass value="1.0"/>
      <inertia ixx="0.01" iyy="0.01" izz="0.01" ixy="0" ixz="0" iyz="0"/>
    </inertial>
  </link>

  <link name="forearm"/>

  <joint name="elbow" type="revolute">
    <parent link="base_link"/>
    <child link="forearm"/>
    <axis xyz="0 0 1"/>
    <!-- Physics go here, but Gazebo often ignores them without a plugin -->
    <limit lower="-1.57" upper="1.57" effort="10" velocity="1.0"/>
  </joint>

</robot>

Use URDF when:

  • You're working in a ROS-based pipeline (RViz, MoveIt, robot_state_publisher)
  • Your robot has no closed chains (most industrial arms, mobile bases)
  • You need maximum tool compatibility — everything reads URDF

URDF's hard limits:

  • No closed kinematic chains (no parallel mechanisms, no four-bar linkages)
  • No world-level properties (gravity, ground plane, lights)
  • Physics parameters are robot-defined but simulator-enforced — a joint's damping in URDF may be completely ignored by your simulator without extra config

SDF: Simulation Description Format

SDF extends the idea of URDF to describe entire worlds, not just robots. A .sdf file can contain models, lights, a ground plane, physics solver settings, and multiple robots — all in one file. It also supports closed kinematic chains via <joint> elements that reference links by name rather than enforcing a tree.

<!-- SDF world with a robot and environment -->
<sdf version="1.9">
  <world name="my_world">

    <physics type="ode">
      <gravity>0 0 -9.81</gravity>
      <max_step_size>0.001</max_step_size>
    </physics>

    <model name="ground">
      <static>true</static>
      <link name="ground_plane">
        <collision name="col">
          <geometry><plane><normal>0 0 1</normal></plane></geometry>
        </collision>
      </link>
    </model>

    <model name="my_arm">
      <link name="base_link">
        <inertial><mass>1.0</mass></inertial>
        <visual name="vis">
          <geometry><box><size>0.1 0.1 0.1</size></box></geometry>
        </visual>
      </link>

      <joint name="elbow" type="revolute">
        <!-- Links referenced by name — no strict parent/child tree required -->
        <parent>base_link</parent>
        <child>forearm</child>
        <axis>
          <xyz>0 0 1</xyz>
          <dynamics><damping>0.1</damping><friction>0.01</friction></dynamics>
        </dynamics>
      </joint>
    </model>

  </world>
</sdf>

Use SDF when:

  • You're using Gazebo (Classic or Ignition/Gz) as your primary simulator
  • You need to describe a full simulation environment, not just a robot
  • Your robot has closed kinematic chains
  • You need reliable physics parameters (SDF damping and friction actually get applied)

SDF's limits:

  • Heavier to write and maintain than URDF for simple robots
  • Poor native support outside the Gazebo ecosystem
  • ROS tooling (RViz, MoveIt) prefers URDF — you'll convert back anyway

MJCF: MuJoCo XML Format

MJCF is the format for MuJoCo, the physics engine now maintained by Google DeepMind. It was designed from the ground up for contact-rich simulation and reinforcement learning. The biggest structural difference: MJCF uses a <body> hierarchy with <geom> children for collision, rather than separate <link> and <collision> elements. It also has first-class support for tendons, actuators, and equality constraints.

<!-- MJCF model for a simple arm -->
<mujoco model="my_arm">

  <option gravity="0 0 -9.81" timestep="0.002"/>

  <worldbody>
    <light pos="0 0 3" dir="0 0 -1"/>
    <geom name="floor" type="plane" size="5 5 0.1" rgba="0.8 0.8 0.8 1"/>

    <body name="base" pos="0 0 0.5">
      <geom type="box" size="0.05 0.05 0.05" rgba="0.2 0.4 0.8 1"/>

      <body name="forearm" pos="0 0 0.1">
        <!-- Joint defined inside the child body — no separate joint element -->
        <joint name="elbow" type="hinge" axis="0 0 1" range="-90 90"
               damping="0.1" frictionloss="0.01"/>
        <geom type="capsule" size="0.03" fromto="0 0 0 0 0 0.2"/>
      </body>
    </body>
  </worldbody>

  <!-- Actuators defined separately from joints -->
  <actuator>
    <motor joint="elbow" gear="1" ctrlrange="-10 10"/>
  </actuator>

  <!-- Equality constraints for closed chains -->
  <equality>
    <weld body1="forearm" body2="some_other_body" solimp="0.9 0.95 0.001"/>
  </equality>

</mujoco>

Use MJCF when:

  • You're training RL policies (IsaacGym and MuJoCo are the dominant RL simulation backends)
  • Contact dynamics matter — dexterous manipulation, legged locomotion, cloth
  • You need tendons, muscles, or underactuated systems
  • Simulation speed is critical (MuJoCo is fast)

MJCF's limits:

  • Minimal ROS integration — you'll need wrappers or separate URDF for deployment
  • Steeper learning curve for the actuator/sensor/equality constraint system
  • Less community tooling than URDF for visualization and motion planning

Converting Between Formats

For URDF → SDF, Gazebo ships a converter:

# Convert URDF to SDF
gz sdf -p my_robot.urdf > my_robot.sdf

# Validate the result
gz sdf --check my_robot.sdf

For URDF → MJCF, the mujoco-py ecosystem includes mjcf utilities, but the most reliable path is the urdf2mjcf tool:

pip install urdf2mjcf
urdf2mjcf my_robot.urdf --output my_robot.xml

# Always check actuator mappings — they don't transfer automatically

What breaks in conversion:

  • Closed chains: URDF can't express them, so they're lost going URDF → SDF/MJCF. Fix manually.
  • Actuators: MJCF decouples actuators from joints. You'll define these after conversion.
  • Mesh paths: Each format handles relative paths differently. Audit <mesh> references after every conversion.

Verification

After any conversion, sanity-check the physical properties:

# For MuJoCo — verify inertia and mass loaded correctly
import mujoco
import numpy as np

model = mujoco.MjModel.from_xml_path("my_robot.xml")
for i in range(model.nbody):
    name = model.body(i).name
    mass = model.body(i).mass
    print(f"{name}: mass={mass:.3f} kg")
# For Gazebo SDF — check joint limits loaded
gz model -m my_arm --joint-info

You should see: Mass values matching your design spec. If you see 0.0 mass on any non-world body, your inertial block didn't transfer.


What You Learned

  • URDF is the lingua franca of ROS robotics, but its tree-only structure and weak physics spec create real problems downstream.
  • SDF is URDF's full-featured sibling for Gazebo — use it when you own the simulator environment.
  • MJCF is purpose-built for RL and contact-rich simulation; it's the right choice when physics accuracy and training speed matter more than ROS integration.
  • Conversion tools exist but always require manual review of actuators, constraints, and mesh paths.

Limitation: If you're deploying on real hardware via ROS 2, you'll maintain a URDF regardless of which simulation format you use. Treat URDF as your source-of-truth for the physical robot and SDF/MJCF as derived simulation artifacts.


Tested with ROS 2 Jazzy, Gazebo Harmonic, and MuJoCo 3.x on Ubuntu 24.04