How-to

Migrating from ROS 2 Humble to Lyrical Luth: A Step-by-Step Checklist

The Kopik team7 min read

According to the official release table, ROS 2 Humble Hawksbill reaches end-of-life in May 2027. Lyrical Luth, released on May 22, 2026, is the current Long Term Support release and is supported until May 2031. Moving from Humble to Lyrical skips three releases (Iron, Jazzy and Kilted), so you inherit every breaking change made in between. Expect a new Ubuntu base, several removed C++ headers and deprecated CMake macros, and a new set of discovery variables. The checklist below lists them in the order you will run into them.

Each item comes from the release notes and migration guides in the ROS 2 documentation, Lyrical branch. Before you change a CMakeLists.txt, you can check any detail against the source in the ROS 2 knowledge base.

Step 1: Put the support dates on your roadmap

ROS 2 distributions relevant to a Humble upgrade (official release table)

DistributionRelease dateEnd-of-lifeStatus on October 5, 2026
Humble HawksbillMay 23, 2022May 2027Supported
Iron IrwiniMay 23, 2023December 4, 2024End-of-life
Jazzy JaliscoMay 23, 2024May 2029Supported
Kilted KaijuMay 23, 2025December 2026Supported, ends this year
Lyrical LuthMay 22, 2026May 2031Supported (LTS)
Makoa Mata-mataMay 2027 (planned)December 2028Future regular release

For a US robotics startup with robots in the field, the takeaway is simple. Kilted ends in December 2026, so it is not a sensible landing spot. Jazzy is supported until May 2029. Lyrical gives the longest runway, until May 2031. The Lyrical timeline is explicit about what EOL means: the distribution will stop receiving updates, including security updates.

Step 2: Plan the operating system and toolchain jump

Humble's Tier 1 platforms were Ubuntu 22.04 (Jammy) on amd64 and arm64, and Windows 10 with Visual Studio 2019. Lyrical's support table is different:

  • Ubuntu Resolute (26.04): Tier 1 on amd64 and arm64, with Debian packages and pre-built archives.
  • Windows 11 (VS2022): Tier 1 on amd64.
  • RHEL 10: Tier 2 on amd64.
  • Ubuntu Noble (24.04): Tier 3 only, with an early EOL. Noble is supported until 2029-06-01. On any Tier 3 platform you must build Lyrical from source.
  • Language minimums: C++20, C17, and Python 3.12 to 3.14.

In practice, moving a Humble fleet onto Tier 1 Lyrical binaries means moving from Ubuntu 22.04 to Ubuntu 26.04. Budget time for re-imaging robots and for any vendor drivers that must be rebuilt against the new OS.

Step 3: Don't mix distributions on the same network

The documentation is clear that nodes are not guaranteed to be able to communicate across distributions. Its example is a Humble node talking to an Iron node: it may or may not work, but it is not supported. The same caution applies to mixing middleware vendors. The docs recommend that every part of a distributed system uses the same ROS version and the same RMW implementation. Plan to cut over one complete system at a time, including the robot, its operator station and any cloud bridge, rather than node by node.

Step 4: Fix the C++ and Python APIs removed after Humble

These items were deprecated in or before Humble and later removed. They are the ones that break a build:

  • Subscription callbacks (removed in Jazzy): void callback(std::shared_ptr<MessageT>) and the variant with const rclcpp::MessageInfo & are gone. Switch to std::shared_ptr<const MessageT>.
  • tf2 headers (removed in Jazzy): tf2_eigen/tf2_eigen.h, tf2_geometry_msgs/tf2_geometry_msgs.h, tf2_kdl/tf2_kdl.h, tf2_sensor_msgs/tf2_sensor_msgs.h and tf2_bullet/tf2_bullet.h must become the .hpp versions.
  • rclcpp/qos_event.hpp (removed in Jazzy): use rclcpp/event_handler.hpp.
  • RCLCPP_SCOPE_EXIT (removed in Iron): use RCPPUTILS_SCOPE_EXIT.
  • rcutils/get_env.h (removed in Iron): use rcutils/env.h.
  • rclcpp::get_typesupport_handle (deprecated in Jazzy): use rclcpp::get_message_typesupport_handle.
  • tf2 Buffer: since Jazzy, wait_for_transform_async and wait_for_transform_full_async return a future containing the transform information instead of true or false.
  • Lyrical message buffers: in C++, all uint8[] fields now have the type rosidl::Buffer<uint8_t> instead of std::vector<uint8_t>. Review code that manipulates image or point-cloud byte arrays.

Some behavior changes will not show up at compile time. Since Iron, the multi-threaded executor in rclcpp and rclpy defaults to one thread per CPU, or 2 if the OS cannot report a count. Since Jazzy, callbacks in the default executors are no longer ordered consistently, even within the same entity. If your tests assumed a particular callback order, expect them to fail.

Step 5: Update CMake, launch files and package.xml

  1. Replace ament_target_dependencies(), deprecated in Kilted, with target_link_libraries() and modern CMake targets. The deprecation warning suggests a call with a scope keyword such as PUBLIC. If the target already uses plain target_link_libraries() (some macros such as ament_add_gtest do this), drop the keyword, or CMake errors with “All uses of target_link_libraries with a target must be either all-keyword or all-plain.”
  2. Rename launch_ros.actions.RosTimer to ROSTimer and PushRosNamespace to PushROSNamespace (Iron). Replace LaunchConfigurationEquals and LaunchConfigurationNotEquals with the Equals and NotEquals substitutions.
  3. Audit package.xml. ROS 2 requires format 2 or higher, and colcon uses the REP 149 specification. If some packages were ported from ROS 1 and still use run_depend, replace it with exec_depend (needed at run time) and/or build_export_depend (needed by packages that build against yours). If you are unsure, the docs say to use both.
  4. Check Fast DDS configuration. In Kilted, fastrtps was renamed to fastdds. The rmw implementation names stay the same, but XML profile environment strings change.

Step 6: Revisit discovery, bags and tooling

Iron deprecated ROS_LOCALHOST_ONLY in favor of ROS_AUTOMATIC_DISCOVERY_RANGE. The default is SUBNET, and the other values are LOCALHOST, OFF and SYSTEM_DEFAULT. ROS_STATIC_PEERS takes a semicolon-separated list of addresses to reach specific machines. Update your systemd units and launch environments accordingly.

On the data side, rosbag2 has written mcap by default since Iron. The sqlite3 format remains available, and both can be played back. Jazzy renamed the --exclude option to --exclude-regex. Lyrical adds --max-bag-files for circular recording and services to start and stop recording remotely.

After installing, confirm the environment with printenv | grep -i ROS (ROS_DISTRO should read lyrical). In Lyrical, ros2 doctor --report also lists actions, services and ROS environment variables.

Step 7: Collect the Lyrical payoff

Once the port compiles and the tests pass, a few Lyrical features can justify the effort to the rest of the business:

  • EventsCBGExecutor (rclcpp): according to the release notes, it uses 10% to 15% less CPU than the Single-Threaded and Multi-Threaded executors and supports multiple threads and multiple sources of ROS time. Instantiate rclcpp::executors::EventsCBGExecutor, or start a component container with ros2 run rclcpp_components component_container --executor-type events-cbg.
  • AsyncNode (rclpy): runs an asyncio event loop so callbacks can await service calls. The notes say it uses significantly less CPU than the default SingleThreadedExecutor.
  • Debugging: ros2 service info --verbose shows service QoS profiles, and ros2 topic bw accepts several topics or --all.
  • Field data: rosbag2 can now be controlled remotely through services, and ros2 bag record --all --max-bag-size 100000000 --max-bag-files 5 keeps a rolling window on robots with small disks.

Treat these as follow-up work rather than part of the port. Changing the executor changes threading behavior, so benchmark it on your own nodes before rolling it out to the whole fleet.

Migration checklist in one line per release

Iron: discovery variables, mcap, executor threads, removed macros. Jazzy: callback signatures, tf2 .hpp headers, --exclude-regex. Kilted: ament_target_dependencies, fastrtps renamed fastdds. Lyrical: Ubuntu 26.04, C++20, rosidl::Buffer for uint8[].

Check every change against the release notes

The ROS 2 base indexes the official release notes for every distribution, along with the Lyrical concepts, tutorials and migration guides. Ask “Is ROS 2 Humble still supported right now, or do I need to upgrade already?” or paste a deprecation warning, and get the answer with its source passage.

Teams that keep internal upgrade notes can also turn them into their own private base from the knowledge base builder. Note that the ROS 2 base covers the core documentation only. It does not cover the migration notes of third-party stacks such as Nav2 or MoveIt 2.

Frequently asked questions

When does ROS 2 Humble reach end-of-life?

The official distribution table lists May 2027 as the end-of-life date of Humble Hawksbill, which was released on May 23, 2022.

How long is ROS 2 Lyrical Luth supported?

Lyrical Luth is a Long Term Support release, released on May 22, 2026 and supported until May 2031. After that date it stops receiving updates, including security updates.

Can a Humble node talk to a Lyrical node?

This is not guaranteed. The documentation states that nodes are not guaranteed to communicate across distributions; it may or may not work, but it is not supported and should not be relied upon.

Which Ubuntu version does ROS 2 Lyrical use?

Ubuntu Resolute (26.04) is a Tier 1 platform for Lyrical on amd64 and arm64. Ubuntu Noble (24.04) is Tier 3 with an early end-of-life on 2029-06-01, which means building from source.

Should I upgrade from Humble to Kilted first?

The release table gives Kilted Kaiju an end-of-life date of December 2026, so it is a short-lived stop. Jazzy (EOL May 2029) and Lyrical (EOL May 2031, the current LTS) give a longer support window.

Get the Kopik newsletter

New knowledge bases, RAG guides and product news. One email every week or two, unsubscribe in one click.

By subscribing you agree to receive our newsletter. We never share your address.