Skip to main content

ROS 2 MoveIt 2 Guide 2026

MoveIt 2 is the de-facto motion-planning framework for ROS 2. This guide covers setup, SRDF authoring, planning groups, the MoveGroupInterface API, OMPL planner selection, collision objects, Cartesian paths, and trajectory execution.

MoveIt 2 Architecture Overview

Request Flow

Your Node → MoveGroupInterface → /move_group action server

Planning Scene (collision) ←→ OMPL / PILZ / CHOMP planner

Trajectory → FollowJointTrajectory → ros2_control controllers

Installation (Jazzy)

sudo apt install ros-jazzy-moveit

# Optional extras
sudo apt install ros-jazzy-moveit-planners-chomp   # CHOMP planner
sudo apt install ros-jazzy-pilz-industrial-motion-planners  # PILZ PTP/LIN
sudo apt install ros-jazzy-moveit-visual-tools      # RViz helpers

SRDF — Semantic Robot Description Format

MoveIt 2 needs an SRDF alongside your URDF. It defines planning groups, end-effectors, and allowed collisions. Use moveit_setup_assistant or write it manually:

<?xml version="1.0" ?>
<robot name="my_arm">

  <!-- Planning group: all joints from base to flange -->
  <group name="arm">
    <chain base_link="base_link" tip_link="tool0" />
  </group>

  <!-- End-effector group -->
  <group name="gripper">
    <joint name="left_finger_joint" />
    <joint name="right_finger_joint" />
  </group>

  <!-- Named configurations -->
  <group_state name="home" group="arm">
    <joint name="joint1" value="0" />
    <joint name="joint2" value="-1.57" />
    <joint name="joint3" value="0" />
    <joint name="joint4" value="-1.57" />
    <joint name="joint5" value="0" />
    <joint name="joint6" value="0" />
  </group_state>

  <!-- Disable collision between adjacent links -->
  <disable_collisions link1="base_link"   link2="link1" reason="Adjacent" />
  <disable_collisions link1="link1"       link2="link2" reason="Adjacent" />

  <!-- End-effector definition -->
  <end_effector name="gripper" parent_link="tool0" group="gripper" />
</robot>

MoveGroupInterface — C++ Quick Start

#include <moveit/move_group_interface/move_group_interface.h>
#include <moveit/planning_scene_interface/planning_scene_interface.h>
#include <geometry_msgs/msg/pose.hpp>

// In your node's constructor / callback
auto move_group = moveit::planning_interface::MoveGroupInterface(node, "arm");
auto planning_scene = moveit::planning_interface::PlanningSceneInterface();

// ── Goal pose ──────────────────────────────────────────────────────
geometry_msgs::msg::Pose target;
target.orientation.w = 1.0;
target.position.x    = 0.4;
target.position.y    = 0.0;
target.position.z    = 0.5;
move_group.setPoseTarget(target);

// ── Planning ────────────────────────────────────────────────────────
moveit::planning_interface::MoveGroupInterface::Plan plan;
bool ok = (move_group.plan(plan) == moveit::core::MoveItErrorCode::SUCCESS);

// ── Execute ─────────────────────────────────────────────────────────
if (ok) move_group.execute(plan);

Common API Calls

MethodPurpose
setPoseTarget(pose)Set Cartesian end-effector goal
setJointValueTarget(state)Set named or numeric joint goal
setNamedTarget("home")Use a named SRDF group state
setPositionTolerance(0.001)Meters — default 1mm
setOrientationTolerance(0.01)Radians — default ~0.57°
setMaxVelocityScalingFactor(0.5)Fraction of URDF joint limits
setMaxAccelerationScalingFactor(0.3)Fraction of URDF limits
getPlanningFrame()Returns the planning frame (world/base_link)
getEndEffectorLink()Returns the name of the EE link
move()Plan + execute in one call (blocking)
asyncMove()Non-blocking move
stop()Halt execution immediately

OMPL Planner Selection

PlannerBest ForTradeoff
RRTConnectGeneral 6-DOF armsFast but not optimal
RRTstarAsymptotically optimal pathsSlow — use with long timeout
PRMRepeated queries in same environmentExpensive to build, fast to query
TRRTTorque/energy minimizationNeeds cost map
BKPIECE1High-DOF / narrow passagesBalanced exploration
# Set planner via API
move_group.setPlannerId("RRTConnectkConfigDefault");
move_group.setPlanningTime(10.0);  // seconds

# Or in YAML (ompl_planning.yaml)
arm:
  default_planner_config: RRTConnectkConfigDefault
  planner_configs:
    - RRTConnectkConfigDefault
    - RRTstarkConfigDefault

Collision Objects and Planning Scene

#include <moveit_msgs/msg/collision_object.hpp>
#include <shape_msgs/msg/solid_primitive.hpp>

moveit_msgs::msg::CollisionObject box;
box.header.frame_id = move_group.getPlanningFrame();
box.id = "table";

// Define a box shape
shape_msgs::msg::SolidPrimitive primitive;
primitive.type = primitive.BOX;
primitive.dimensions = {1.0, 0.5, 0.05};  // x y z meters

geometry_msgs::msg::Pose pose;
pose.orientation.w = 1.0;
pose.position.x    = 0.5;
pose.position.y    = 0.0;
pose.position.z    = 0.3;

box.primitives.push_back(primitive);
box.primitive_poses.push_back(pose);
box.operation = box.ADD;

// Apply to planning scene
planning_scene.applyCollisionObject(box);

// Attach an object to the end-effector (after grasping)
move_group.attachObject("box_to_pick", "tool0", {"left_finger", "right_finger"});

// Detach
move_group.detachObject("box_to_pick");

Cartesian Paths

Cartesian paths move the EE through a list of waypoints in a straight line, useful for grasping and welding.

std::vector<geometry_msgs::msg::Pose> waypoints;

// Start from current pose
waypoints.push_back(move_group.getCurrentPose().pose);

// Move +10 cm in Z
geometry_msgs::msg::Pose wp1 = waypoints[0];
wp1.position.z += 0.1;
waypoints.push_back(wp1);

// Move +20 cm in X
geometry_msgs::msg::Pose wp2 = wp1;
wp2.position.x += 0.2;
waypoints.push_back(wp2);

moveit_msgs::msg::RobotTrajectory trajectory;
double fraction = move_group.computeCartesianPath(
    waypoints,
    0.01,    // eef_step  — interpolation resolution (meters)
    0.0,     // jump_threshold — 0 to disable
    trajectory
);

RCLCPP_INFO(logger, "Cartesian path (%.2f%% achieved)", fraction * 100.0);
if (fraction > 0.95) move_group.execute(trajectory);

Minimal MoveIt 2 Launch File

from launch import LaunchDescription
from launch_ros.actions import Node
from moveit_configs_utils import MoveItConfigsBuilder

def generate_launch_description():
    moveit_config = (
        MoveItConfigsBuilder("my_arm")
        .robot_description(file_path="config/my_arm.urdf.xacro")
        .robot_description_semantic(file_path="config/my_arm.srdf")
        .trajectory_execution(file_path="config/moveit_controllers.yaml")
        .planning_pipelines(pipelines=["ompl", "pilz_industrial_motion_planner"])
        .to_moveit_configs()
    )

    return LaunchDescription([
        Node(
            package="moveit_ros_move_group",
            executable="move_group",
            output="screen",
            parameters=[moveit_config.to_dict()],
        ),
        Node(
            package="rviz2",
            executable="rviz2",
            arguments=["-d", "config/moveit.rviz"],
            parameters=[moveit_config.to_dict()],
        ),
    ])

Controller Configuration

MoveIt 2 sends trajectories to ros2_control via FollowJointTrajectory action. Configure in moveit_controllers.yaml:

moveit_controller_manager: moveit_simple_controller_manager/MoveItSimpleControllerManager

moveit_simple_controller_manager:
  controller_names:
    - arm_controller
    - gripper_controller

arm_controller:
  type: FollowJointTrajectory
  action_ns: follow_joint_trajectory
  default: true
  joints:
    - joint1
    - joint2
    - joint3
    - joint4
    - joint5
    - joint6

gripper_controller:
  type: FollowJointTrajectory
  action_ns: follow_joint_trajectory
  default: true
  joints:
    - left_finger_joint
    - right_finger_joint

Common Issues

Planning fails immediately

Check SRDF disable_collisions — initial state might be in self-collision. Run check_state_validity or increase allowed_planning_time.

IK solution not found

End-effector goal is out of reach. Print getEndEffectorLink() and verify URDF joint limits allow the pose.

Controller not found

moveit_controllers.yaml action_ns must match the action server name from ros2 action list. Add /arm_controller/ prefix if namespaced.

Trajectory execution aborted

Joint trajectory tolerances too tight. Loosen goal_time_tolerance or path_tolerance in the controller.

Collision object not visible

applyCollisionObject() call reached wrong node. Check PlanningSceneInterface constructor node context.

Next Steps