You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2026/05/04 09:56:45 UTC

(camel) branch main updated: CAMEL-23402: Route structure dump to walk model for structure to be accurate (#22887)

This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new c2dd3bfae090 CAMEL-23402: Route structure dump to walk model for structure to be accurate (#22887)
c2dd3bfae090 is described below

commit c2dd3bfae090421b6a1dfabc85b2507f8d72ce5d
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Mon May 4 11:56:38 2026 +0200

    CAMEL-23402: Route structure dump to walk model for structure to be accurate (#22887)
    
    * CAMEL-23402: Route structure dump to walk model for structure to be accurate
---
 .../java/org/apache/camel/spi/ModelDumpLine.java   |  16 +-
 .../camel/impl/engine/AbstractCamelContext.java    |  11 +-
 .../impl/console/RouteStructureDevConsole.java     |   6 +
 .../camel/impl/DefaultModelToStructureDumper.java  |  99 +++++-------
 .../org/apache/camel/model/CatchDefinition.java    |   4 +-
 .../camel/model/ProcessorDefinitionHelper.java     |   4 +
 .../management/DumpRouteStructureChoiceTest.java   | 170 +++++++++++++++++++++
 .../management/DumpRouteStructureDoTryTest.java    | 156 +++++++++++++++++++
 .../camel/management/DumpRouteStructureTest.java   |  33 +++-
 .../camel-jbang-cmd-route-structure.adoc           |   1 +
 .../META-INF/camel-jbang-commands-metadata.json    |   2 +-
 .../commands/action/CamelRouteStructureAction.java |  13 ++
 12 files changed, 436 insertions(+), 79 deletions(-)

diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/ModelDumpLine.java b/core/camel-api/src/main/java/org/apache/camel/spi/ModelDumpLine.java
index 8b880b6500c2..81470a3f5a4c 100644
--- a/core/camel-api/src/main/java/org/apache/camel/spi/ModelDumpLine.java
+++ b/core/camel-api/src/main/java/org/apache/camel/spi/ModelDumpLine.java
@@ -17,13 +17,15 @@
 package org.apache.camel.spi;
 
 /**
- * Represents a line in a model dumper of the route structure (not with full details like a XML or YAML dump).
+ * Represents a line in a model dumper of the route structure (not with full details such as XML or YAML dump).
  *
- * @param location line source location:line (if present)
- * @param type     the kind of EIP node
- * @param id       the id of the EIP node
- * @param level    indent level of the EIP node
- * @param code     EIP code such as label or short name that is human-readable or pseudocode
+ * @param location    line source scheme:location:line (if present)
+ * @param type        the kind of EIP node
+ * @param id          the id of the EIP node
+ * @param level       indent level of the EIP node
+ * @param code        EIP code such as label or short name that is human-readable or pseudocode
+ * @param description optional description of the EIP node
  */
-public record ModelDumpLine(String location, String type, String id, int level, String code) {
+public record ModelDumpLine(String location, String type, String id, int level, String code, String description) {
+
 }
diff --git a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java
index b648c21ccdb5..e19ed149107c 100644
--- a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java
+++ b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java
@@ -3174,6 +3174,12 @@ public abstract class AbstractCamelContext extends BaseService
             LOG.debug("Skip starting routes as CamelContext has been configured with autoStartup=false");
         }
 
+        // dump routes when as we have model before creating the runtime models, which can help during troubleshooting
+        // when routes have problems starting
+        if (getDumpRoutes() != null && !"false".equals(getDumpRoutes())) {
+            doDumpRoutes();
+        }
+
         if (!getRouteController().isSupervising()) {
             // invoke this logic to warmup the routes and if possible also start the routes (using default route controller)
             StartupStep subStep
@@ -3185,11 +3191,6 @@ public abstract class AbstractCamelContext extends BaseService
             startupStepRecorder.endStep(subStep);
         }
 
-        // dump routes after they have been initialized and started so we have full information
-        if (getDumpRoutes() != null && !"false".equals(getDumpRoutes())) {
-            doDumpRoutes();
-        }
-
         // ensure extra dev consoles is loaded in case additional JARs has been dynamically added to the classpath
         if (devConsole) {
             StartupStep step = startupStepRecorder.beginStep(CamelContext.class, null, "Scan DevConsoles (phase 2)");
diff --git a/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteStructureDevConsole.java b/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteStructureDevConsole.java
index f25b0368980b..3512b33f2a21 100644
--- a/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteStructureDevConsole.java
+++ b/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteStructureDevConsole.java
@@ -114,6 +114,9 @@ public class RouteStructureDevConsole extends AbstractDevConsole {
             if (mrb.getSourceLocation() != null) {
                 jo.put("source", mrb.getSourceLocation());
             }
+            if (mrb.getDescription() != null) {
+                jo.put("description", mrb.getDescription());
+            }
 
             try {
                 ModelToStructureDumper dumper = PluginHelper.getModelToStructureDumper(getCamelContext());
@@ -184,6 +187,9 @@ public class RouteStructureDevConsole extends AbstractDevConsole {
             c.put("type", line.type());
             c.put("id", line.id());
             c.put("level", line.level());
+            if (line.description() != null) {
+                c.put("description", line.description());
+            }
             c.put("code", Jsoner.escape(line.code()));
             code.add(c);
         }
diff --git a/core/camel-core-engine/src/main/java/org/apache/camel/impl/DefaultModelToStructureDumper.java b/core/camel-core-engine/src/main/java/org/apache/camel/impl/DefaultModelToStructureDumper.java
index 353762780d0e..e93bf1668c9a 100644
--- a/core/camel-core-engine/src/main/java/org/apache/camel/impl/DefaultModelToStructureDumper.java
+++ b/core/camel-core-engine/src/main/java/org/apache/camel/impl/DefaultModelToStructureDumper.java
@@ -17,17 +17,14 @@
 package org.apache.camel.impl;
 
 import java.util.ArrayList;
-import java.util.Comparator;
 import java.util.List;
-import java.util.Set;
-
-import javax.management.MBeanServer;
-import javax.management.ObjectName;
 
 import org.apache.camel.CamelContext;
-import org.apache.camel.Route;
-import org.apache.camel.api.management.mbean.ManagedProcessorMBean;
+import org.apache.camel.NamedNode;
+import org.apache.camel.model.EndpointRequiredDefinition;
 import org.apache.camel.model.Model;
+import org.apache.camel.model.OptionalIdentifiedDefinition;
+import org.apache.camel.model.ProcessorDefinitionHelper;
 import org.apache.camel.model.RouteDefinition;
 import org.apache.camel.spi.ModelDumpLine;
 import org.apache.camel.spi.ModelToStructureDumper;
@@ -40,73 +37,61 @@ public class DefaultModelToStructureDumper implements ModelToStructureDumper {
 
     @Override
     public List<ModelDumpLine> dumpStructure(CamelContext context, String routeId, boolean brief) throws Exception {
+        // dump in text format padded by level
         List<ModelDumpLine> answer = new ArrayList<>();
 
         // lookup model and runtime route
         final Model model = context.getCamelContextExtension().getContextPlugin(Model.class);
         final RouteDefinition def = model.getRouteDefinition(routeId);
-        final Route route = context.getRoute(routeId);
-        // dump in text format padded by level
         String scheme = def.getResource() != null ? def.getResource().getScheme() : "file";
 
-        String loc
-                = scheme + ":" + (route != null ? route.getSourceLocationShort() : LoggerHelper.getLineNumberLoggerName(def));
-        answer.add(new ModelDumpLine(loc, "route", def.getRouteId(), 0, "route[" + def.getRouteId() + "]"));
-        String uri;
-        if (route != null) {
-            uri = brief ? route.getEndpoint().getEndpointBaseUri() : route.getEndpoint().getEndpointUri();
-        } else {
-            uri = def.getInput().getEndpointUri();
-            if (brief) {
-                uri = StringHelper.before(uri, "?", uri);
-            }
+        String loc = scheme + ":" + LoggerHelper.getLineNumberLoggerName(def);
+        answer.add(
+                new ModelDumpLine(loc, "route", def.getRouteId(), 0, "route[" + def.getRouteId() + "]", def.getDescription()));
+        String uri = def.getInput().getLabel();
+        if (brief) {
+            uri = StringHelper.before(uri, "?", uri);
         }
-        answer.add(new ModelDumpLine(loc, "from", routeId, 1, "from[" + uri + "]"));
+        answer.add(new ModelDumpLine(loc, "from", routeId, 1, "from[" + uri + "]", def.getDescription()));
 
-        MBeanServer server = context.getManagementStrategy().getManagementAgent().getMBeanServer();
-        if (server != null) {
-            String jmxDomain = context.getManagementStrategy().getManagementAgent().getMBeanObjectDomainName();
-            // get all the processor mbeans and sort them accordingly to their index
-            String prefix = context.getManagementStrategy().getManagementAgent().getIncludeHostName() ? "*/" : "";
-            ObjectName query = ObjectName.getInstance(
-                    jmxDomain + ":context=" + prefix + context.getManagementName() + ",type=processors,*");
-            Set<ObjectName> names = server.queryNames(query, null);
-            List<ManagedProcessorMBean> mps = new ArrayList<>();
-            for (ObjectName on : names) {
-                ManagedProcessorMBean processor = context.getManagementStrategy().getManagementAgent().newProxyClient(on,
-                        ManagedProcessorMBean.class);
-                // the processor must belong to this route
-                if (def.getRouteId().equals(processor.getRouteId())) {
-                    mps.add(processor);
-                }
+        var outputs = ProcessorDefinitionHelper.filterTypeInOutputs(def.getOutputs(), OptionalIdentifiedDefinition.class);
+        for (var output : outputs) {
+            loc = scheme + ":" + output.getLocation();
+            if (output.getLineNumber() > 0) {
+                loc += ":" + output.getLineNumber();
             }
-            // sort by index
-            mps.sort(new OrderProcessorMBeans());
-
-            // dump in text format padded by level
-            for (ManagedProcessorMBean processor : mps) {
-                // include scheme in loc
-                loc = scheme + ":" + processor.getSourceLocationShort();
-                String kind = processor.getProcessorName();
-                String id = processor.getProcessorId();
-                int level = processor.getLevel() + 1;
-                String code = brief ? processor.getProcessorName() : processor.getModelLabel();
-                answer.add(new ModelDumpLine(loc, kind, id, level, code));
+            String kind = output.getShortName();
+            String id = output.getId();
+            int level = getLevel(output) + 1;
+            boolean choice = "choice".equals(output.getShortName());
+            String code = choice || brief ? output.getShortName() : output.getLabel();
+            // even in brief mode we want to see the uri for EIPs
+            if (brief && output instanceof EndpointRequiredDefinition erd) {
+                uri = StringHelper.before(erd.getEndpointUri(), "?", erd.getEndpointUri());
+                code = output.getShortName() + "[" + uri + "]";
             }
+            String desc = output.getDescription();
+            answer.add(new ModelDumpLine(loc, kind, id, level, code, desc));
         }
 
         return answer;
     }
 
-    /**
-     * Used for sorting the processor mbeans accordingly to their index.
-     */
-    private static final class OrderProcessorMBeans implements Comparator<ManagedProcessorMBean> {
-
-        @Override
-        public int compare(ManagedProcessorMBean o1, ManagedProcessorMBean o2) {
-            return o1.getIndex().compareTo(o2.getIndex());
+    private static int getLevel(NamedNode node) {
+        int level = 0;
+        while (node != null && node.getParent() != null) {
+            // special for choice
+            boolean choice = "choice".equals(node.getParent().getShortName());
+            if (choice) {
+                level++;
+            }
+            boolean shallow = "when".equals(node.getShortName()) || "otherwise".equals(node.getShortName());
+            if (!shallow) {
+                level++;
+            }
+            node = node.getParent();
         }
+        return level;
     }
 
 }
diff --git a/core/camel-core-model/src/main/java/org/apache/camel/model/CatchDefinition.java b/core/camel-core-model/src/main/java/org/apache/camel/model/CatchDefinition.java
index 10173bd5ee54..208d29aa7b6d 100644
--- a/core/camel-core-model/src/main/java/org/apache/camel/model/CatchDefinition.java
+++ b/core/camel-core-model/src/main/java/org/apache/camel/model/CatchDefinition.java
@@ -75,7 +75,7 @@ public class CatchDefinition extends OutputDefinition<CatchDefinition> {
 
     @Override
     public String toString() {
-        return "DoCatch[ " + getExceptionClasses() + " -> " + getOutputs() + "]";
+        return "DoCatch[" + String.join(",", getExceptions()) + " -> " + getOutputs() + "]";
     }
 
     @Override
@@ -85,7 +85,7 @@ public class CatchDefinition extends OutputDefinition<CatchDefinition> {
 
     @Override
     public String getLabel() {
-        return "doCatch[ " + getExceptionClasses() + "]";
+        return "doCatch[" + String.join(",", getExceptions()) + "]";
     }
 
     @Override
diff --git a/core/camel-core-model/src/main/java/org/apache/camel/model/ProcessorDefinitionHelper.java b/core/camel-core-model/src/main/java/org/apache/camel/model/ProcessorDefinitionHelper.java
index 74b50756febe..ef437cbd326e 100644
--- a/core/camel-core-model/src/main/java/org/apache/camel/model/ProcessorDefinitionHelper.java
+++ b/core/camel-core-model/src/main/java/org/apache/camel/model/ProcessorDefinitionHelper.java
@@ -307,6 +307,10 @@ public final class ProcessorDefinitionHelper {
 
                     // otherwise is optional
                     if (choice.getOtherwise() != null) {
+                        OtherwiseDefinition other = choice.getOtherwise();
+                        if (type.isInstance(other)) {
+                            found.add((T) other);
+                        }
                         List<ProcessorDefinition<?>> children = choice.getOtherwise().getOutputs();
                         doFindType(children, type, found, ++current, maxDeep);
                     }
diff --git a/core/camel-management/src/test/java/org/apache/camel/management/DumpRouteStructureChoiceTest.java b/core/camel-management/src/test/java/org/apache/camel/management/DumpRouteStructureChoiceTest.java
new file mode 100644
index 000000000000..6e4e0fbfb822
--- /dev/null
+++ b/core/camel-management/src/test/java/org/apache/camel/management/DumpRouteStructureChoiceTest.java
@@ -0,0 +1,170 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.management;
+
+import java.util.List;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.TestSupportNodeIdFactory;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.spi.ModelDumpLine;
+import org.apache.camel.spi.ModelToStructureDumper;
+import org.apache.camel.spi.NodeIdFactory;
+import org.apache.camel.support.PluginHelper;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.DisabledOnOs;
+import org.junit.jupiter.api.condition.OS;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+@DisabledOnOs(OS.AIX)
+public class DumpRouteStructureChoiceTest extends ManagementTestSupport {
+
+    @Override
+    protected CamelContext createCamelContext() throws Exception {
+        CamelContext context = super.createCamelContext();
+        context.getCamelContextExtension().addContextPlugin(NodeIdFactory.class, new TestSupportNodeIdFactory());
+        return context;
+    }
+
+    @Override
+    public boolean isUseRouteBuilder() {
+        return false;
+    }
+
+    @Test
+    public void testDump() throws Exception {
+        TestSupportNodeIdFactory.resetCounters();
+
+        context.addRoutes(createRouteBuilder());
+        context.start();
+
+        ModelToStructureDumper dumper = PluginHelper.getModelToStructureDumper(context);
+        List<ModelDumpLine> lines = dumper.dumpStructure(context, "myChoiceRoute", false);
+        assertEquals(10, lines.size());
+        assertEquals(0, lines.get(0).level());
+        assertEquals("route", lines.get(0).type());
+        assertEquals("myChoiceRoute", lines.get(0).id());
+        assertEquals("route[myChoiceRoute]", lines.get(0).code());
+        assertEquals(1, lines.get(1).level());
+        assertEquals("from", lines.get(1).type());
+        assertEquals("myChoiceRoute", lines.get(1).id());
+        assertEquals("from[direct:start]", lines.get(1).code());
+        assertEquals(2, lines.get(2).level());
+        assertEquals("choice", lines.get(2).type());
+        assertEquals("choice1", lines.get(2).id());
+        assertEquals("choice", lines.get(2).code());
+        assertEquals(3, lines.get(3).level());
+        assertEquals("when", lines.get(3).type());
+        assertEquals("when1", lines.get(3).id());
+        assertEquals("when[xpath{$foo = 'bar'}]", lines.get(3).code());
+        assertEquals(4, lines.get(4).level());
+        assertEquals("to", lines.get(4).type());
+        assertEquals("to1", lines.get(4).id());
+        assertEquals("to[mock:x]", lines.get(4).code());
+        assertEquals(3, lines.get(5).level());
+        assertEquals("when", lines.get(5).type());
+        assertEquals("when2", lines.get(5).id());
+        assertEquals("when[xpath{$foo = 'cheese'}]", lines.get(5).code());
+        assertEquals(4, lines.get(6).level());
+        assertEquals("to", lines.get(6).type());
+        assertEquals("to2", lines.get(6).id());
+        assertEquals("to[mock:y]", lines.get(6).code());
+        assertEquals(3, lines.get(7).level());
+        assertEquals("otherwise", lines.get(7).type());
+        assertEquals("otherwise1", lines.get(7).id());
+        assertEquals("otherwise", lines.get(7).code());
+        assertEquals(4, lines.get(8).level());
+        assertEquals("to", lines.get(8).type());
+        assertEquals("to3", lines.get(8).id());
+        assertEquals("to[mock:z]", lines.get(8).code());
+        assertEquals(2, lines.get(9).level());
+        assertEquals("to", lines.get(9).type());
+        assertEquals("to4", lines.get(9).id());
+        assertEquals("to[mock:end]", lines.get(9).code());
+    }
+
+    @Test
+    public void testDumpBrief() throws Exception {
+        TestSupportNodeIdFactory.resetCounters();
+
+        context.addRoutes(createRouteBuilder());
+        context.start();
+
+        ModelToStructureDumper dumper = PluginHelper.getModelToStructureDumper(context);
+        List<ModelDumpLine> lines = dumper.dumpStructure(context, "myChoiceRoute", true);
+        assertEquals(10, lines.size());
+        assertEquals(0, lines.get(0).level());
+        assertEquals("route", lines.get(0).type());
+        assertEquals("myChoiceRoute", lines.get(0).id());
+        assertEquals("route[myChoiceRoute]", lines.get(0).code());
+        assertEquals(1, lines.get(1).level());
+        assertEquals("from", lines.get(1).type());
+        assertEquals("myChoiceRoute", lines.get(1).id());
+        assertEquals("from[direct:start]", lines.get(1).code());
+        assertEquals(2, lines.get(2).level());
+        assertEquals("choice", lines.get(2).type());
+        assertEquals("choice1", lines.get(2).id());
+        assertEquals("choice", lines.get(2).code());
+        assertEquals(3, lines.get(3).level());
+        assertEquals("when", lines.get(3).type());
+        assertEquals("when1", lines.get(3).id());
+        assertEquals("when", lines.get(3).code());
+        assertEquals(4, lines.get(4).level());
+        assertEquals("to", lines.get(4).type());
+        assertEquals("to1", lines.get(4).id());
+        assertEquals("to[mock:x]", lines.get(4).code());
+        assertEquals(3, lines.get(5).level());
+        assertEquals("when", lines.get(5).type());
+        assertEquals("when2", lines.get(5).id());
+        assertEquals("when", lines.get(5).code());
+        assertEquals(4, lines.get(6).level());
+        assertEquals("to", lines.get(6).type());
+        assertEquals("to2", lines.get(6).id());
+        assertEquals("to[mock:y]", lines.get(6).code());
+        assertEquals(3, lines.get(7).level());
+        assertEquals("otherwise", lines.get(7).type());
+        assertEquals("otherwise1", lines.get(7).id());
+        assertEquals("otherwise", lines.get(7).code());
+        assertEquals(4, lines.get(8).level());
+        assertEquals("to", lines.get(8).type());
+        assertEquals("to3", lines.get(8).id());
+        assertEquals("to[mock:z]", lines.get(8).code());
+        assertEquals(2, lines.get(9).level());
+        assertEquals("to", lines.get(9).type());
+        assertEquals("to4", lines.get(9).id());
+        assertEquals("to[mock:end]", lines.get(9).code());
+    }
+
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                context.setDebugging(true);
+
+                from("direct:start").routeId("myChoiceRoute")
+                        .choice()
+                            .when().xpath("$foo = 'bar'").to("mock:x")
+                            .when().xpath("$foo = 'cheese'").to("mock:y")
+                            .otherwise().to("mock:z")
+                        .end()
+                    .to("mock:end");
+            }
+        };
+    }
+
+}
diff --git a/core/camel-management/src/test/java/org/apache/camel/management/DumpRouteStructureDoTryTest.java b/core/camel-management/src/test/java/org/apache/camel/management/DumpRouteStructureDoTryTest.java
new file mode 100644
index 000000000000..f602115c1ec4
--- /dev/null
+++ b/core/camel-management/src/test/java/org/apache/camel/management/DumpRouteStructureDoTryTest.java
@@ -0,0 +1,156 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.management;
+
+import java.util.List;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.TestSupportNodeIdFactory;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.spi.ModelDumpLine;
+import org.apache.camel.spi.ModelToStructureDumper;
+import org.apache.camel.spi.NodeIdFactory;
+import org.apache.camel.support.PluginHelper;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.DisabledOnOs;
+import org.junit.jupiter.api.condition.OS;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+@DisabledOnOs(OS.AIX)
+public class DumpRouteStructureDoTryTest extends ManagementTestSupport {
+
+    @Override
+    protected CamelContext createCamelContext() throws Exception {
+        CamelContext context = super.createCamelContext();
+        context.getCamelContextExtension().addContextPlugin(NodeIdFactory.class, new TestSupportNodeIdFactory());
+        return context;
+    }
+
+    @Override
+    public boolean isUseRouteBuilder() {
+        return false;
+    }
+
+    @Test
+    public void testDump() throws Exception {
+        TestSupportNodeIdFactory.resetCounters();
+
+        context.addRoutes(createRouteBuilder());
+        context.start();
+
+        ModelToStructureDumper dumper = PluginHelper.getModelToStructureDumper(context);
+        List<ModelDumpLine> lines = dumper.dumpStructure(context, "myDoTryRoute", false);
+        assertEquals(8, lines.size());
+        assertEquals(0, lines.get(0).level());
+        assertEquals("route", lines.get(0).type());
+        assertEquals("myDoTryRoute", lines.get(0).id());
+        assertEquals("route[myDoTryRoute]", lines.get(0).code());
+        assertEquals(1, lines.get(1).level());
+        assertEquals("from", lines.get(1).type());
+        assertEquals("myDoTryRoute", lines.get(1).id());
+        assertEquals("from[direct:start]", lines.get(1).code());
+        assertEquals(2, lines.get(2).level());
+        assertEquals("doTry", lines.get(2).type());
+        assertEquals("doTry1", lines.get(2).id());
+        assertEquals("doTry", lines.get(2).code());
+        assertEquals(3, lines.get(3).level());
+        assertEquals("split", lines.get(3).type());
+        assertEquals("split1", lines.get(3).id());
+        assertEquals("split[tokenize(body, ,)]", lines.get(3).code());
+        assertEquals(4, lines.get(4).level());
+        assertEquals("to", lines.get(4).type());
+        assertEquals("to1", lines.get(4).id());
+        assertEquals("to[mock:line]", lines.get(4).code());
+        assertEquals(3, lines.get(5).level());
+        assertEquals("doCatch", lines.get(5).type());
+        assertEquals("doCatch1", lines.get(5).id());
+        assertEquals("doCatch[java.lang.IllegalArgumentException]", lines.get(5).code());
+        assertEquals(4, lines.get(6).level());
+        assertEquals("to", lines.get(6).type());
+        assertEquals("to2", lines.get(6).id());
+        assertEquals("to[mock:iae]", lines.get(6).code());
+        assertEquals(2, lines.get(7).level());
+        assertEquals("to", lines.get(7).type());
+        assertEquals("to3", lines.get(7).id());
+        assertEquals("to[mock:end]", lines.get(7).code());
+    }
+
+    @Test
+    public void testDumpBrief() throws Exception {
+        TestSupportNodeIdFactory.resetCounters();
+
+        context.addRoutes(createRouteBuilder());
+        context.start();
+
+        ModelToStructureDumper dumper = PluginHelper.getModelToStructureDumper(context);
+        List<ModelDumpLine> lines = dumper.dumpStructure(context, "myDoTryRoute", true);
+        assertEquals(8, lines.size());
+        assertEquals(0, lines.get(0).level());
+        assertEquals("route", lines.get(0).type());
+        assertEquals("myDoTryRoute", lines.get(0).id());
+        assertEquals("route[myDoTryRoute]", lines.get(0).code());
+        assertEquals(1, lines.get(1).level());
+        assertEquals("from", lines.get(1).type());
+        assertEquals("myDoTryRoute", lines.get(1).id());
+        assertEquals("from[direct:start]", lines.get(1).code());
+        assertEquals(2, lines.get(2).level());
+        assertEquals("doTry", lines.get(2).type());
+        assertEquals("doTry1", lines.get(2).id());
+        assertEquals("doTry", lines.get(2).code());
+        assertEquals(3, lines.get(3).level());
+        assertEquals("split", lines.get(3).type());
+        assertEquals("split1", lines.get(3).id());
+        assertEquals("split", lines.get(3).code());
+        assertEquals(4, lines.get(4).level());
+        assertEquals("to", lines.get(4).type());
+        assertEquals("to1", lines.get(4).id());
+        assertEquals("to[mock:line]", lines.get(4).code());
+        assertEquals(3, lines.get(5).level());
+        assertEquals("doCatch", lines.get(5).type());
+        assertEquals("doCatch1", lines.get(5).id());
+        assertEquals("doCatch", lines.get(5).code());
+        assertEquals(4, lines.get(6).level());
+        assertEquals("to", lines.get(6).type());
+        assertEquals("to2", lines.get(6).id());
+        assertEquals("to[mock:iae]", lines.get(6).code());
+        assertEquals(2, lines.get(7).level());
+        assertEquals("to", lines.get(7).type());
+        assertEquals("to3", lines.get(7).id());
+        assertEquals("to[mock:end]", lines.get(7).code());
+    }
+
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                context.setDebugging(true);
+
+                from("direct:start").routeId("myDoTryRoute")
+                        .doTry()
+                            .split(body().tokenize(","))
+                                .to("mock:line")
+                        .endDoTry()
+                        .doCatch(IllegalArgumentException.class)
+                            .to("mock:iae")
+                        .end()
+                        .to("mock:end");
+            }
+        };
+    }
+
+}
diff --git a/core/camel-management/src/test/java/org/apache/camel/management/DumpRouteStructureTest.java b/core/camel-management/src/test/java/org/apache/camel/management/DumpRouteStructureTest.java
index 1b6345225e32..7874708d2cb0 100644
--- a/core/camel-management/src/test/java/org/apache/camel/management/DumpRouteStructureTest.java
+++ b/core/camel-management/src/test/java/org/apache/camel/management/DumpRouteStructureTest.java
@@ -18,9 +18,12 @@ package org.apache.camel.management;
 
 import java.util.List;
 
+import org.apache.camel.CamelContext;
+import org.apache.camel.TestSupportNodeIdFactory;
 import org.apache.camel.builder.RouteBuilder;
 import org.apache.camel.spi.ModelDumpLine;
 import org.apache.camel.spi.ModelToStructureDumper;
+import org.apache.camel.spi.NodeIdFactory;
 import org.apache.camel.support.PluginHelper;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.condition.DisabledOnOs;
@@ -31,8 +34,17 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
 @DisabledOnOs(OS.AIX)
 public class DumpRouteStructureTest extends ManagementTestSupport {
 
+    @Override
+    protected CamelContext createCamelContext() throws Exception {
+        CamelContext context = super.createCamelContext();
+        context.getCamelContextExtension().addContextPlugin(NodeIdFactory.class, new TestSupportNodeIdFactory());
+        return context;
+    }
+
     @Test
     public void testDump() throws Exception {
+        TestSupportNodeIdFactory.resetCounters();
+
         ModelToStructureDumper dumper = PluginHelper.getModelToStructureDumper(context);
         List<ModelDumpLine> lines = dumper.dumpStructure(context, "myOtherRoute", false);
         assertEquals(5, lines.size());
@@ -40,18 +52,22 @@ public class DumpRouteStructureTest extends ManagementTestSupport {
         assertEquals("route", lines.get(0).type());
         assertEquals("myOtherRoute", lines.get(0).id());
         assertEquals("route[myOtherRoute]", lines.get(0).code());
+        assertEquals("my cool route", lines.get(0).description());
         assertEquals(1, lines.get(1).level());
         assertEquals("from", lines.get(1).type());
         assertEquals("myOtherRoute", lines.get(1).id());
-        assertEquals("from[seda://bar?multipleConsumers=true&size=1234]", lines.get(1).code());
+        assertEquals("from[seda:bar?size=1234&multipleConsumers=true]", lines.get(1).code());
+        assertEquals("my cool route", lines.get(1).description());
         assertEquals(2, lines.get(2).level());
         assertEquals("filter", lines.get(2).type());
         assertEquals("myfilter", lines.get(2).id());
         assertEquals("filter[header{bar}]", lines.get(2).code());
+        assertEquals("my special filter", lines.get(2).description());
         assertEquals(3, lines.get(3).level());
         assertEquals("to", lines.get(3).type());
         assertEquals("mybar", lines.get(3).id());
         assertEquals("to[mock:bar]", lines.get(3).code());
+        assertEquals("my great bar", lines.get(3).description());
         assertEquals(2, lines.get(4).level());
         assertEquals("to", lines.get(4).type());
         assertEquals("myend", lines.get(4).id());
@@ -60,6 +76,8 @@ public class DumpRouteStructureTest extends ManagementTestSupport {
 
     @Test
     public void testDumpBrief() throws Exception {
+        TestSupportNodeIdFactory.resetCounters();
+
         ModelToStructureDumper dumper = PluginHelper.getModelToStructureDumper(context);
         List<ModelDumpLine> lines = dumper.dumpStructure(context, "myOtherRoute", true);
         assertEquals(5, lines.size());
@@ -70,7 +88,7 @@ public class DumpRouteStructureTest extends ManagementTestSupport {
         assertEquals(1, lines.get(1).level());
         assertEquals("from", lines.get(1).type());
         assertEquals("myOtherRoute", lines.get(1).id());
-        assertEquals("from[seda://bar]", lines.get(1).code());
+        assertEquals("from[seda:bar]", lines.get(1).code());
         assertEquals(2, lines.get(2).level());
         assertEquals("filter", lines.get(2).type());
         assertEquals("myfilter", lines.get(2).id());
@@ -78,11 +96,12 @@ public class DumpRouteStructureTest extends ManagementTestSupport {
         assertEquals(3, lines.get(3).level());
         assertEquals("to", lines.get(3).type());
         assertEquals("mybar", lines.get(3).id());
-        assertEquals("to", lines.get(3).code());
+        assertEquals("to[mock:bar]", lines.get(3).code());
+        assertEquals("my great bar", lines.get(3).description());
         assertEquals(2, lines.get(4).level());
         assertEquals("to", lines.get(4).type());
         assertEquals("myend", lines.get(4).id());
-        assertEquals("to", lines.get(4).code());
+        assertEquals("to[log:end]", lines.get(4).code());
     }
 
     protected RouteBuilder createRouteBuilder() {
@@ -91,9 +110,9 @@ public class DumpRouteStructureTest extends ManagementTestSupport {
             public void configure() {
                 context.setDebugging(true);
 
-                from("seda:bar?size=1234&multipleConsumers=true").routeId("myOtherRoute")
-                        .filter().header("bar").id("myfilter")
-                            .to("mock:bar").id("mybar")
+                from("seda:bar?size=1234&multipleConsumers=true").routeId("myOtherRoute").routeDescription("my cool route")
+                        .filter().header("bar").id("myfilter").description("my special filter")
+                        .to("mock:bar").id("mybar").description("my great bar")
                         .end()
                         .to("log:end").id("myend");
             }
diff --git a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-cmd-route-structure.adoc b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-cmd-route-structure.adoc
index df29ba23c0ca..8ce1fdceb2b5 100644
--- a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-cmd-route-structure.adoc
+++ b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-cmd-route-structure.adoc
@@ -20,6 +20,7 @@ camel cmd route-structure [options]
 |===
 | Option | Description | Default | Type
 | `--brief` | To show less detailed route structure |  | boolean
+| `--description` | To show description instead of code |  | boolean
 | `--filter` | Filter route by filename or route id (multiple names can be separated by comma) |  | String
 | `--ignore-loading-error` | Whether to ignore route loading and compilation errors (use this with care!) | false | boolean
 | `--json` | Output in JSON Format |  | boolean
diff --git a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
index f8ee79c83aa9..377232871720 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
+++ b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
@@ -2,7 +2,7 @@
   "commands": [
     { "name": "bind", "fullName": "bind", "description": "DEPRECATED: Bind source and sink Kamelets as a new Camel integration", "deprecated": true, "sourceClass": "org.apache.camel.dsl.jbang.core.commands.bind.Bind", "options": [ { "names": "--error-handler", "description": "Add error handler (none|log|sink:<endpoint>). Sink endpoints are expected in the format [[apigroup\/]version:]kind:[namespace\/]name, plain Camel URIs or Kamelet name.", "javaType": "java.lang.String", "type": "stri [...]
     { "name": "catalog", "fullName": "catalog", "description": "List artifacts from Camel Catalog", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.catalog.CatalogCommand", "options": [ { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": "component", "fullName": "catalog component", "description": "List components from the Camel Catalog", "sourceClass": "org.apache.camel.dsl.jbang.co [...]
-    { "name": "cmd", "fullName": "cmd", "description": "Performs commands in the running Camel integrations, such as start\/stop route, or change logging levels.", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.action.CamelAction", "options": [ { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": "browse", "fullName": "cmd browse", "description": "Browse pending messages on endpoints [...]
+    { "name": "cmd", "fullName": "cmd", "description": "Performs commands in the running Camel integrations, such as start\/stop route, or change logging levels.", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.action.CamelAction", "options": [ { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": "browse", "fullName": "cmd browse", "description": "Browse pending messages on endpoints [...]
     { "name": "completion", "fullName": "completion", "description": "Generate completion script for bash\/zsh", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Complete", "options": [ { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] },
     { "name": "config", "fullName": "config", "description": "Get and set user configuration values", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.config.ConfigCommand", "options": [ { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": "get", "fullName": "config get", "description": "Display user configuration value", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.config. [...]
     { "name": "debug", "fullName": "debug", "description": "Debug local Camel integration", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Debug", "options": [ { "names": "--ago", "description": "Use ago instead of yyyy-MM-dd HH:mm:ss in timestamp.", "javaType": "boolean", "type": "boolean" }, { "names": "--background", "description": "Run in the background", "defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names": "--background-wait", "description": "To  [...]
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelRouteStructureAction.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelRouteStructureAction.java
index f69f913f0948..ba70f8275a1e 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelRouteStructureAction.java
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelRouteStructureAction.java
@@ -68,6 +68,10 @@ public class CamelRouteStructureAction extends ActionBaseCommand {
                         description = "To show less detailed route structure")
     boolean brief;
 
+    @CommandLine.Option(names = { "--description" },
+                        description = "To show description instead of code")
+    boolean description;
+
     @CommandLine.Option(names = { "--filter" },
                         description = "Filter route by filename or route id (multiple names can be separated by comma)")
     String filter;
@@ -139,6 +143,14 @@ public class CamelRouteStructureAction extends ActionBaseCommand {
                                 code.id = line.getString("id");
                                 code.level = line.getInteger("level");
                                 code.code = line.getString("code");
+                                code.description = line.getString("description");
+                                if (brief) {
+                                    // make code brief
+                                    code.code = StringHelper.before(code.code, "[", code.code);
+                                } else if (description) {
+                                    // show description
+                                    code.code = code.type + (code.description != null ? "[" + code.description + "]" : "");
+                                }
                                 row.code.add(code);
                             }
                         }
@@ -320,6 +332,7 @@ public class CamelRouteStructureAction extends ActionBaseCommand {
         String id;
         int level;
         String code;
+        String description;
     }
 
 }