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 91c0f732343c CAMEL-25771: camel-core - a route checksum and revision, 
so tools can tell which routes changed between two runs or after a reload 
(#28213)
91c0f732343c is described below

commit 91c0f732343c2df48f2c7649801c96288553db82
Author: Claus Ibsen <[email protected]>
AuthorDate: Thu Oct 22 22:15:00 2026 +0200

    CAMEL-25771: camel-core - a route checksum and revision, so tools can tell 
which routes changed between two runs or after a reload (#28213)
    
    Co-authored-by: Guillaume Nodet <[email protected]>
    Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
---
 .../apache/camel/catalog/dev-consoles-openapi.json |  16 ++
 .../camel/catalog/dev-consoles/route-dump.json     |   4 +
 .../catalog/dev-consoles/route-structure.json      |   4 +
 .../apache/camel/catalog/dev-consoles/route.json   |   8 +
 .../kamelet/KameletRouteChecksumTest.java          |  72 ++++++
 .../src/main/java/org/apache/camel/NamedRoute.java |  15 ++
 .../src/main/java/org/apache/camel/Route.java      |  28 +++
 .../apache/camel/spi/ModelChecksumGenerator.java   |  53 ++++
 .../camel/impl/engine/AbstractCamelContext.java    |  52 ++++
 .../org/apache/camel/impl/engine/DefaultRoute.java |  18 ++
 .../org/apache/camel/impl/engine/RouteService.java |   5 +
 .../org/apache/camel/dev-console/route-dump.json   |   4 +
 .../apache/camel/dev-console/route-structure.json  |   4 +
 .../org/apache/camel/dev-console/route.json        |   8 +
 .../apache/camel/impl/console/RouteDevConsole.java |   8 +-
 .../camel/impl/console/RouteDumpDevConsole.java    |   7 +-
 .../impl/console/RouteStructureDevConsole.java     |   7 +-
 .../impl/console/RouteChecksumDevConsoleTest.java  |  76 ++++++
 .../camel/impl/DefaultDumpRoutesStrategy.java      |   5 +
 .../org/apache/camel/model/RouteDefinition.java    |  15 ++
 .../apache/camel/model/RouteDefinitionHelper.java  |  24 ++
 .../org/apache/camel/impl/RouteChecksumTest.java   | 271 +++++++++++++++++++++
 .../RouteWatcherReloadChangedRoutesTest.java       | 161 ++++++++++++
 .../api/management/mbean/ManagedRouteMBean.java    |   6 +
 .../camel/management/mbean/ManagedRoute.java       |  10 +
 .../camel/management/ManagedRouteChecksumTest.java |  59 +++++
 .../camel/support/RouteWatcherReloadStrategy.java  |  26 +-
 .../org/apache/camel/model-checksum-generator      |   2 +
 .../apache/camel/xml/LwModelChecksumGenerator.java |  63 +++++
 .../org/apache/camel/xml/LwModelToXMLDumper.java   | 120 ++++++++-
 .../camel/xml/LwModelToXMLDumperRawUriTest.java    |  52 ++++
 .../ROOT/pages/camel-jbang-diagnostics.adoc        |  13 +
 .../modules/ROOT/pages/camel-jbang-managing.adoc   |   5 +
 .../jbang-commands/camel-jbang-get-route.adoc      |   1 +
 .../jbang-commands/camel-jbang-top-route.adoc      |   1 +
 .../modules/ROOT/pages/route-reload.adoc           |  16 ++
 docs/user-manual/modules/ROOT/pages/routes.adoc    |  39 +++
 .../endpoint/EndpointDslRouteChecksumTest.java     |  55 +++++
 .../META-INF/camel-jbang-commands-metadata.json    |   4 +-
 .../jbang/core/commands/TransformJavaRoutes.java   |   9 +
 .../commands/action/CamelRouteStructureAction.java |   6 +
 .../core/commands/process/CamelRouteStatus.java    |  16 ++
 .../jbang/core/commands/process/CamelRouteTop.java |   5 +
 .../commands/action/RouteActionJavaSourceTest.java |  56 +++++
 .../commands/process/CamelRouteStatusTest.java     |  28 +++
 .../dsl/jbang/core/commands/mcp/RuntimeTools.java  |   8 +-
 46 files changed, 1444 insertions(+), 21 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles-openapi.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles-openapi.json
index 865ca3654eca..6c30c57da90d 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles-openapi.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles-openapi.json
@@ -5924,6 +5924,14 @@
                                                                                
                                "type": "string",
                                                                                
                                "description": "The source that declares the 
templated route the route was created from (only present when known)"
                                                                                
                        },
+                                                                               
                        "checksum": {
+                                                                               
                                "type": "string",
+                                                                               
                                "description": "The checksum of the route as 
written, the same in every run: compare it with an earlier run to tell whether 
the route changed (only present when known, as it needs camel-xml-io)"
+                                                                               
                        },
+                                                                               
                        "revision": {
+                                                                               
                                "type": "integer",
+                                                                               
                                "description": "The revision of the route in 
this run: 1 when it is added, one more each time it is added again with another 
checksum, such as by a reload (only present when the checksum is known)"
+                                                                               
                        },
                                                                                
                        "state": {
                                                                                
                                "type": "string",
                                                                                
                                "description": "The route state"
@@ -6678,6 +6686,10 @@
                                                                                
                                "type": "string",
                                                                                
                                "description": "The source location (only 
present when known)"
                                                                                
                        },
+                                                                               
                        "checksum": {
+                                                                               
                                "type": "string",
+                                                                               
                                "description": "The checksum of the route as 
written, the same in every run: the dump is of this version of the route (only 
present when known, as it needs camel-xml-io)"
+                                                                               
                        },
                                                                                
                        "format": {
                                                                                
                                "type": "string",
                                                                                
                                "description": "The dump format, 
xml\/yaml\/java (only present when the dump succeeded)"
@@ -6974,6 +6986,10 @@
                                                                                
                                "type": "string",
                                                                                
                                "description": "The route description (only 
present when configured)"
                                                                                
                        },
+                                                                               
                        "checksum": {
+                                                                               
                                "type": "string",
+                                                                               
                                "description": "The checksum of the route as 
written, the same in every run: the facts a tool keeps per line hold for this 
version of the route (only present when known, as it needs camel-xml-io)"
+                                                                               
                        },
                                                                                
                        "code": {
                                                                                
                                "type": "array",
                                                                                
                                "items": {
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/route-dump.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/route-dump.json
index dc181764ef54..558f375eb34d 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/route-dump.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/route-dump.json
@@ -96,6 +96,10 @@
               "type": "string",
               "description": "The source location (only present when known)"
             },
+            "checksum": {
+              "type": "string",
+              "description": "The checksum of the route as written, the same 
in every run: the dump is of this version of the route (only present when 
known, as it needs camel-xml-io)"
+            },
             "format": {
               "type": "string",
               "description": "The dump format, xml\/yaml\/java (only present 
when the dump succeeded)"
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/route-structure.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/route-structure.json
index 34190b878fc1..f0e228f960d1 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/route-structure.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/route-structure.json
@@ -100,6 +100,10 @@
               "type": "string",
               "description": "The route description (only present when 
configured)"
             },
+            "checksum": {
+              "type": "string",
+              "description": "The checksum of the route as written, the same 
in every run: the facts a tool keeps per line hold for this version of the 
route (only present when known, as it needs camel-xml-io)"
+            },
             "code": {
               "type": "array",
               "items": {
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/route.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/route.json
index 20556d0d9c73..b92777039028 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/route.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/route.json
@@ -133,6 +133,14 @@
               "type": "string",
               "description": "The source that declares the templated route the 
route was created from (only present when known)"
             },
+            "checksum": {
+              "type": "string",
+              "description": "The checksum of the route as written, the same 
in every run: compare it with an earlier run to tell whether the route changed 
(only present when known, as it needs camel-xml-io)"
+            },
+            "revision": {
+              "type": "integer",
+              "description": "The revision of the route in this run: 1 when it 
is added, one more each time it is added again with another checksum, such as 
by a reload (only present when the checksum is known)"
+            },
             "state": {
               "type": "string",
               "description": "The route state"
diff --git 
a/components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletRouteChecksumTest.java
 
b/components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletRouteChecksumTest.java
new file mode 100644
index 000000000000..a181aeaf6c94
--- /dev/null
+++ 
b/components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletRouteChecksumTest.java
@@ -0,0 +1,72 @@
+/*
+ * 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.component.kamelet;
+
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Route;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+/**
+ * CAMEL-25771: a Kamelet route gets its node prefix, and its id when it has 
none of its own, from a counter, which the
+ * checksum leaves out: the same Kamelet route has the same checksum each time 
it is created.
+ */
+class KameletRouteChecksumTest {
+
+    @Test
+    void theSameKameletRouteHasTheSameChecksumWhenCreatedAgain() throws 
Exception {
+        // the counter goes on in the second run, as it does when a reload 
creates the routes again
+        Map<String, String> first = checksums();
+        Map<String, String> second = checksums();
+
+        assertEquals(2, first.size(), first.toString());
+        first.values().forEach(checksum -> assertNotNull(checksum, 
first.toString()));
+        assertEquals(first, second);
+    }
+
+    // the checksums of the Kamelet routes: the one with an id of its own, and 
the one without
+    private static Map<String, String> checksums() throws Exception {
+        try (CamelContext context = new DefaultCamelContext()) {
+            context.addRoutes(new RouteBuilder() {
+                @Override
+                public void configure() {
+                    routeTemplate("greeting").templateParameter("name")
+                            .from("kamelet:source")
+                            .setBody().simple("Hello {{name}}");
+
+                    
from("direct:own").routeId("own").kamelet("greeting/greet-own?name=own");
+                    
from("direct:none").routeId("none").kamelet("greeting?name=none");
+                }
+            });
+            context.start();
+            Map<String, String> answer = new TreeMap<>();
+            for (Route route : context.getRoutes()) {
+                if ("greeting".equals(route.getRouteTemplateId())) {
+                    answer.put("greet-own".equals(route.getId()) ? "own" : 
"none", route.getChecksum());
+                }
+            }
+            return answer;
+        }
+    }
+}
diff --git a/core/camel-api/src/main/java/org/apache/camel/NamedRoute.java 
b/core/camel-api/src/main/java/org/apache/camel/NamedRoute.java
index b2ec7e1058fc..1363186f910a 100644
--- a/core/camel-api/src/main/java/org/apache/camel/NamedRoute.java
+++ b/core/camel-api/src/main/java/org/apache/camel/NamedRoute.java
@@ -100,4 +100,19 @@ public interface NamedRoute {
         return null;
     }
 
+    /**
+     * Gets the checksum of the route as written: its steps, options and 
expressions, with property placeholders as
+     * written, without the ids Camel generates and where the route is in its 
source. The same route gives the same
+     * checksum in every run, so tools can tell which routes changed. It is 
computed when the route is prepared, before
+     * the parts of route configurations are added to it.
+     *
+     * @return the checksum, or null when it is not known (camel-xml-io is not 
on the classpath)
+     * @see    org.apache.camel.spi.ModelChecksumGenerator
+     * @since  4.24
+     */
+    @Nullable
+    default String getChecksum() {
+        return null;
+    }
+
 }
diff --git a/core/camel-api/src/main/java/org/apache/camel/Route.java 
b/core/camel-api/src/main/java/org/apache/camel/Route.java
index 7d1ca3dd1819..847034802837 100644
--- a/core/camel-api/src/main/java/org/apache/camel/Route.java
+++ b/core/camel-api/src/main/java/org/apache/camel/Route.java
@@ -218,6 +218,34 @@ public interface Route extends RuntimeConfiguration {
         return null;
     }
 
+    /**
+     * Gets the checksum of the route as written: its steps, options and 
expressions, with property placeholders as
+     * written, without the ids Camel generates and where the route is in its 
source, and with the secrets in endpoint
+     * URIs masked. The same route gives the same checksum in every run, so 
tools can tell which routes changed between
+     * two runs.
+     *
+     * @return the checksum, or null when it is not known (camel-xml-io is not 
on the classpath)
+     * @see    org.apache.camel.spi.ModelChecksumGenerator
+     * @since  4.24
+     */
+    @Nullable
+    default String getChecksum() {
+        return null;
+    }
+
+    /**
+     * Gets the revision of the route in this run: 1 when the route is added, 
one more each time it is added again with
+     * another checksum, such as when a reload changed it. A reload that 
brings the route back as it was keeps the
+     * revision. The revision follows the route id, and starts over when the 
application restarts (tools compare
+     * checksums across runs). Camel keeps the revisions of the 10000 route 
ids used last.
+     *
+     * @return the revision, or 0 when it is not known (the checksum is not 
known)
+     * @since  4.24
+     */
+    default int getRevision() {
+        return 0;
+    }
+
     /**
      * The source:line-number where the route input is located in the source 
code
      */
diff --git 
a/core/camel-api/src/main/java/org/apache/camel/spi/ModelChecksumGenerator.java 
b/core/camel-api/src/main/java/org/apache/camel/spi/ModelChecksumGenerator.java
new file mode 100644
index 000000000000..91342aa76539
--- /dev/null
+++ 
b/core/camel-api/src/main/java/org/apache/camel/spi/ModelChecksumGenerator.java
@@ -0,0 +1,53 @@
+/*
+ * 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.spi;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.NamedNode;
+import org.jspecify.annotations.Nullable;
+
+/**
+ * Computes the checksum of a route as written, so tools can tell which routes 
changed between two runs or after a
+ * reload: the same route gives the same checksum in every run.
+ * <p/>
+ * The checksum is a hash of the whole route: its steps, their options and 
expressions, with property placeholders as
+ * written. It leaves out what does not change the route: the ids Camel 
generates, and where the route is in its source
+ * (moving the route in its file, or adding a route above it, keeps its 
checksum).
+ * <p/>
+ * The implementation is provided by {@code camel-xml-io}, which writes every 
option of the model, and is discovered via
+ * the service key {@link #FACTORY}. It is a single implementation, so a route 
gets the same checksum whichever modules
+ * an application has.
+ *
+ * @since 4.24
+ */
+public interface ModelChecksumGenerator {
+
+    /**
+     * Service factory key.
+     */
+    String FACTORY = "model-checksum-generator";
+
+    /**
+     * Computes the checksum of the definition as written.
+     *
+     * @param  context    the CamelContext
+     * @param  definition the definition, such as a route
+     * @return            the checksum, a short hex digest, or <tt>null</tt> 
when it cannot be computed
+     */
+    @Nullable
+    String generateChecksum(CamelContext context, NamedNode definition);
+}
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 1641883748e7..0bd7b42e86ed 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
@@ -139,6 +139,7 @@ import org.apache.camel.spi.ManagementNameStrategy;
 import org.apache.camel.spi.ManagementStrategy;
 import org.apache.camel.spi.MessageHistoryFactory;
 import org.apache.camel.spi.MessageSizeStrategy;
+import org.apache.camel.spi.ModelChecksumGenerator;
 import org.apache.camel.spi.ModelJAXBContextFactory;
 import org.apache.camel.spi.ModelToJavaDumper;
 import org.apache.camel.spi.ModelToStructureDumper;
@@ -228,6 +229,8 @@ public abstract class AbstractCamelContext extends 
BaseService
         implements CatalogCamelContext, Suspendable {
 
     private static final Logger LOG = 
LoggerFactory.getLogger(AbstractCamelContext.class);
+    // how many route ids keep their revision (CAMEL-25771)
+    private static final int MAX_ROUTE_REVISIONS = 10000;
 
     private final InternalServiceManager internalServiceManager;
 
@@ -251,6 +254,15 @@ public abstract class AbstractCamelContext extends 
BaseService
     private final StopWatch stopWatch = new StopWatch(false);
     private final ThreadLocal<Set<String>> componentsInCreation = 
ThreadLocal.withInitial(HashSet::new);
     private final Lock routesLock = new ReentrantLock();
+    // the revisions of the routes by id, guarded by routesLock (CAMEL-25771): 
the ids used least recently go first
+    // once there are more than MAX_ROUTE_REVISIONS, so an application that 
keeps adding routes with new ids, such as
+    // Kamelet routes without an id of their own, does not grow it for the 
life of the JVM
+    private final Map<String, RouteRevision> routeRevisions = new 
LinkedHashMap<>(16, 0.75f, true) {
+        @Override
+        protected boolean removeEldestEntry(Map.Entry<String, RouteRevision> 
eldest) {
+            return size() > MAX_ROUTE_REVISIONS;
+        }
+    };
     private final Lock lock = new ReentrantLock();
     private VetoCamelContextStartException vetoed;
     private String managementName;
@@ -411,6 +423,7 @@ public abstract class AbstractCamelContext extends 
BaseService
         camelContextExtension.lazyAddContextPlugin(ModelToYAMLDumper.class, 
this::createModelToYAMLDumper);
         camelContextExtension.lazyAddContextPlugin(ModelToJavaDumper.class, 
this::createModelToJavaDumper);
         
camelContextExtension.lazyAddContextPlugin(ModelToStructureDumper.class, 
this::createModelToStructureDumper);
+        
camelContextExtension.lazyAddContextPlugin(ModelChecksumGenerator.class, 
this::createModelChecksumGenerator);
         camelContextExtension.lazyAddContextPlugin(DeferServiceFactory.class, 
this::createDeferServiceFactory);
         
camelContextExtension.lazyAddContextPlugin(AnnotationBasedProcessorFactory.class,
                 this::createAnnotationBasedProcessorFactory);
@@ -4688,6 +4701,15 @@ public abstract class AbstractCamelContext extends 
BaseService
 
     protected abstract ModelToStructureDumper createModelToStructureDumper();
 
+    /**
+     * The generator of the checksums of routes, or <tt>null</tt> without 
camel-xml-io on the classpath: then the routes
+     * have no checksum.
+     */
+    protected ModelChecksumGenerator createModelChecksumGenerator() {
+        return 
ResolverHelper.resolveBootstrapService(getCamelContextReference(), 
ModelChecksumGenerator.FACTORY,
+                ModelChecksumGenerator.class).orElse(null);
+    }
+
     protected abstract RestBindingJaxbDataFormatFactory 
createRestBindingJaxbDataFormatFactory();
 
     protected abstract RestBindingJacksonXmlDataFormatFactory 
createRestBindingJacksonXmlDataFormatFactory();
@@ -4767,11 +4789,41 @@ public abstract class AbstractCamelContext extends 
BaseService
         routesLock.lock();
         try {
             routes.add(route);
+            // RouteService has set it before the route was announced; this is 
for a route added another way
+            reviseRoute(route);
         } finally {
             routesLock.unlock();
         }
     }
 
+    /**
+     * Sets the revision of a route being added: 1 the first time its id is 
added, one more when it is added again with
+     * another checksum (a reload that changed it), the same when it comes 
back as it was. It is kept by route id, so it
+     * outlives the route being removed and added again; the {@value 
#MAX_ROUTE_REVISIONS} ids used last are kept. The
+     * same route revised again keeps its revision, so RouteService sets it 
before the route added event and
+     * {@link #addRoute(Route)} after it.
+     */
+    void reviseRoute(Route route) {
+        String checksum = route.getChecksum();
+        if (route.getId() == null || checksum == null || !(route instanceof 
DefaultRoute defaultRoute)) {
+            return;
+        }
+        routesLock.lock();
+        try {
+            RouteRevision previous = routeRevisions.get(route.getId());
+            int revision = previous == null ? 1
+                    : previous.checksum().equals(checksum) ? 
previous.revision() : previous.revision() + 1;
+            routeRevisions.put(route.getId(), new RouteRevision(checksum, 
revision));
+            defaultRoute.setRevision(revision);
+        } finally {
+            routesLock.unlock();
+        }
+    }
+
+    /** The checksum a route id was last added with, and its revision. */
+    private record RouteRevision(String checksum, int revision) {
+    }
+
     public void removeRoute(Route route) {
         routesLock.lock();
         try {
diff --git 
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultRoute.java
 
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultRoute.java
index 5bf89e352077..03047b959c83 100644
--- 
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultRoute.java
+++ 
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultRoute.java
@@ -82,6 +82,8 @@ public class DefaultRoute extends ServiceSupport implements 
Route {
     private final String sourceLocation;
     private final String sourceLocationShort;
     private final String routeTemplateId;
+    private final String checksum;
+    private volatile int revision;
     private final Resource templatedRouteResource;
     private final List<Processor> eventDrivenProcessors = new ArrayList<>();
     private final List<InterceptStrategy> interceptStrategies = new 
ArrayList<>(0);
@@ -139,6 +141,7 @@ public class DefaultRoute extends ServiceSupport implements 
Route {
         NamedRoute namedRoute = route instanceof NamedRoute nr ? nr : null;
         this.routeTemplateId = namedRoute != null ? 
namedRoute.getRouteTemplateId() : null;
         this.templatedRouteResource = namedRoute != null ? 
namedRoute.getTemplatedRouteResource() : null;
+        this.checksum = namedRoute != null ? namedRoute.getChecksum() : null;
     }
 
     @Override
@@ -233,6 +236,21 @@ public class DefaultRoute extends ServiceSupport 
implements Route {
         return routeTemplateId;
     }
 
+    @Override
+    public String getChecksum() {
+        return checksum;
+    }
+
+    @Override
+    public int getRevision() {
+        return revision;
+    }
+
+    /** Set by the CamelContext when the route is added: see {@link 
Route#getRevision()}. */
+    void setRevision(int revision) {
+        this.revision = revision;
+    }
+
     @Override
     public Resource getTemplatedRouteResource() {
         return templatedRouteResource;
diff --git 
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/RouteService.java
 
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/RouteService.java
index 02e8933fb2a0..7ecd5487182c 100644
--- 
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/RouteService.java
+++ 
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/RouteService.java
@@ -224,6 +224,11 @@ public class RouteService extends ChildServiceSupport {
 
                     startChildServices(route, childServices);
 
+                    // the revision before the route is announced, so 
listeners and JMX see it (CAMEL-25771)
+                    if (camelContext instanceof AbstractCamelContext acc) {
+                        acc.reviseRoute(route);
+                    }
+
                     // fire event
                     EventHelper.notifyRouteAdded(camelContext, route);
                 }
diff --git 
a/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/route-dump.json
 
b/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/route-dump.json
index dc181764ef54..558f375eb34d 100644
--- 
a/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/route-dump.json
+++ 
b/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/route-dump.json
@@ -96,6 +96,10 @@
               "type": "string",
               "description": "The source location (only present when known)"
             },
+            "checksum": {
+              "type": "string",
+              "description": "The checksum of the route as written, the same 
in every run: the dump is of this version of the route (only present when 
known, as it needs camel-xml-io)"
+            },
             "format": {
               "type": "string",
               "description": "The dump format, xml\/yaml\/java (only present 
when the dump succeeded)"
diff --git 
a/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/route-structure.json
 
b/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/route-structure.json
index 34190b878fc1..f0e228f960d1 100644
--- 
a/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/route-structure.json
+++ 
b/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/route-structure.json
@@ -100,6 +100,10 @@
               "type": "string",
               "description": "The route description (only present when 
configured)"
             },
+            "checksum": {
+              "type": "string",
+              "description": "The checksum of the route as written, the same 
in every run: the facts a tool keeps per line hold for this version of the 
route (only present when known, as it needs camel-xml-io)"
+            },
             "code": {
               "type": "array",
               "items": {
diff --git 
a/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/route.json
 
b/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/route.json
index 20556d0d9c73..b92777039028 100644
--- 
a/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/route.json
+++ 
b/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/route.json
@@ -133,6 +133,14 @@
               "type": "string",
               "description": "The source that declares the templated route the 
route was created from (only present when known)"
             },
+            "checksum": {
+              "type": "string",
+              "description": "The checksum of the route as written, the same 
in every run: compare it with an earlier run to tell whether the route changed 
(only present when known, as it needs camel-xml-io)"
+            },
+            "revision": {
+              "type": "integer",
+              "description": "The revision of the route in this run: 1 when it 
is added, one more each time it is added again with another checksum, such as 
by a reload (only present when the checksum is known)"
+            },
             "state": {
               "type": "string",
               "description": "The route state"
diff --git 
a/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteDevConsole.java
 
b/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteDevConsole.java
index 5012dbf2654a..eb956478637c 100644
--- 
a/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteDevConsole.java
+++ 
b/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteDevConsole.java
@@ -97,6 +97,8 @@ public class RouteDevConsole extends AbstractDevConsole {
             @Metadata(description = "Whether the endpoint is remote") boolean 
remote,
             @Metadata(description = "The source location (only present when 
known)") String source,
             @Metadata(description = "The source that declares the templated 
route the route was created from (only present when known)") String 
templatedRouteSource,
+            @Metadata(description = "The checksum of the route as written, the 
same in every run: compare it with an earlier run to tell whether the route 
changed (only present when known, as it needs camel-xml-io)") String checksum,
+            @Metadata(description = "The revision of the route in this run: 1 
when it is added, one more each time it is added again with another checksum, 
such as by a reload (only present when the checksum is known)") Integer 
revision,
             @Metadata(description = "The route state") String state,
             @Metadata(description = "Whether the route supports suspension") 
boolean supportsSuspension,
             @Metadata(description = "Route uptime, human readable text") 
String uptime,
@@ -176,6 +178,9 @@ public class RouteDevConsole extends AbstractDevConsole {
             if (mrb.getTemplatedRouteSource() != null) {
                 sb.append(String.format("%n    Templated Route Source: %s", 
mrb.getTemplatedRouteSource()));
             }
+            if (mrb.getChecksum() != null) {
+                sb.append(String.format("%n    Checksum: %s (revision %d)", 
mrb.getChecksum(), mrb.getRevision()));
+            }
             sb.append(String.format("%n    State: %s", mrb.getState()));
             Route r = getCamelContext().getRoute(mrb.getRouteId());
             sb.append(String.format("%n    Supports Suspension: %s", r != null 
&& r.supportsSuspension()));
@@ -321,7 +326,8 @@ public class RouteDevConsole extends AbstractDevConsole {
             list.add(new RouteEntry(
                     mrb.getRouteId(), mrb.getRouteGroup(), 
mrb.getNodePrefixId(), mrb.getDescription(), mrb.getNote(),
                     mrb.isCreatedByKamelet(), mrb.isCreatedByRouteTemplate(), 
mrb.getRouteTemplateId(), mrb.getEndpointUri(),
-                    mrb.isRemoteEndpoint(), mrb.getSourceLocation(), 
mrb.getTemplatedRouteSource(), mrb.getState(),
+                    mrb.isRemoteEndpoint(), mrb.getSourceLocation(), 
mrb.getTemplatedRouteSource(), mrb.getChecksum(),
+                    mrb.getRevision() > 0 ? mrb.getRevision() : null, 
mrb.getState(),
                     r != null && r.supportsSuspension(), mrb.getUptime(), 
varied > 0 ? varied : null, mixed > 0 ? mixed : null,
                     lastError,
                     stats, procList));
diff --git 
a/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteDumpDevConsole.java
 
b/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteDumpDevConsole.java
index 7e4d707d2b4e..5ab34c280fa1 100644
--- 
a/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteDumpDevConsole.java
+++ 
b/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteDumpDevConsole.java
@@ -53,6 +53,7 @@ public class RouteDumpDevConsole extends AbstractDevConsole {
             @Metadata(description = "The route ID") String routeId,
             @Metadata(description = "The route's endpoint URI") String from,
             @Metadata(description = "The source location (only present when 
known)") String source,
+            @Metadata(description = "The checksum of the route as written, the 
same in every run: the dump is of this version of the route (only present when 
known, as it needs camel-xml-io)") String checksum,
             @Metadata(description = "The dump format, xml/yaml/java (only 
present when the dump succeeded)") String format,
             @Metadata(description = "The dumped route source code (only 
present when the dump succeeded)") List<CodeLine> code) {
     }
@@ -105,6 +106,9 @@ public class RouteDumpDevConsole extends AbstractDevConsole 
{
             if (mrb.getSourceLocation() != null) {
                 sb.append(String.format("%n    Source: %s", 
mrb.getSourceLocation()));
             }
+            if (mrb.getChecksum() != null) {
+                sb.append(String.format("%n    Checksum: %s", 
mrb.getChecksum()));
+            }
             if (dump != null && !dump.isEmpty()) {
                 sb.append("\n\n");
                 for (String line : dump.split("\n")) {
@@ -154,7 +158,8 @@ public class RouteDumpDevConsole extends AbstractDevConsole 
{
                 // ignore
             }
 
-            list.add(new RouteEntry(mrb.getRouteId(), mrb.getEndpointUri(), 
mrb.getSourceLocation(), format, code));
+            list.add(new RouteEntry(
+                    mrb.getRouteId(), mrb.getEndpointUri(), 
mrb.getSourceLocation(), mrb.getChecksum(), format, code));
             return null;
         };
         doCall(options, task);
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 4ca790e6c125..d2cd8200a640 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
@@ -76,6 +76,7 @@ public class RouteStructureDevConsole extends 
AbstractDevConsole {
             @Metadata(description = "The source location (only present when 
known)") String source,
             @Metadata(description = "The source line number (only present when 
known)") Integer line,
             @Metadata(description = "The route description (only present when 
configured)") String description,
+            @Metadata(description = "The checksum of the route as written, the 
same in every run: the facts a tool keeps per line hold for this version of the 
route (only present when known, as it needs camel-xml-io)") String checksum,
             @Metadata(description = "The route's structure, one entry per 
processor (only present when the dump succeeded)") List<CodeEntry> code) {
     }
 
@@ -125,6 +126,9 @@ public class RouteStructureDevConsole extends 
AbstractDevConsole {
                     sb.append(String.format("%n    Source: %s",
                             
extractSourceLocationNoLineNumber(def.getResource().getLocation())));
                 }
+                if (def.getChecksum() != null) {
+                    sb.append(String.format("%n    Checksum: %s", 
def.getChecksum()));
+                }
                 sb.append("\n\n");
                 // the body types line up in a column after the code (a long 
line pushes only its own types out)
                 int width = 0;
@@ -186,7 +190,8 @@ public class RouteStructureDevConsole extends 
AbstractDevConsole {
                 // ignore
             }
 
-            list.add(new RouteEntry(def.getRouteId(), def.getEndpointUrl(), 
source, line, def.getDescription(), code));
+            list.add(new RouteEntry(
+                    def.getRouteId(), def.getEndpointUrl(), source, line, 
def.getDescription(), def.getChecksum(), code));
             return null;
         };
         doCall(options, task);
diff --git 
a/core/camel-console/src/test/java/org/apache/camel/impl/console/RouteChecksumDevConsoleTest.java
 
b/core/camel-console/src/test/java/org/apache/camel/impl/console/RouteChecksumDevConsoleTest.java
new file mode 100644
index 000000000000..2075fe587691
--- /dev/null
+++ 
b/core/camel-console/src/test/java/org/apache/camel/impl/console/RouteChecksumDevConsoleTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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.impl.console;
+
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.console.DevConsole;
+import org.apache.camel.support.PluginHelper;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+/**
+ * CAMEL-25771: the route consoles have the checksum of each route as written, 
and the route console its revision, so a
+ * tool can tell which routes changed and which version of a route the 
structure or the dump is of.
+ */
+class RouteChecksumDevConsoleTest extends ContextTestSupport {
+
+    @Override
+    protected boolean useJmx() {
+        // the route and route-dump consoles list the routes from JMX
+        return true;
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("direct:start").routeId("myRoute").to("mock:result");
+            }
+        };
+    }
+
+    @Test
+    void testChecksumInTheRouteConsoles() {
+        String checksum = context.getRoute("myRoute").getChecksum();
+        Assertions.assertNotNull(checksum);
+
+        JsonObject route = firstRoute("route");
+        Assertions.assertEquals(checksum, route.getString("checksum"));
+        Assertions.assertEquals(1, route.getInteger("revision"));
+        String text = (String) 
console("route").call(DevConsole.MediaType.TEXT);
+        Assertions.assertTrue(text.contains("Checksum: " + checksum + " 
(revision 1)"), text);
+
+        Assertions.assertEquals(checksum, 
firstRoute("route-structure").getString("checksum"));
+        text = (String) 
console("route-structure").call(DevConsole.MediaType.TEXT);
+        Assertions.assertTrue(text.contains("Checksum: " + checksum), text);
+
+        Assertions.assertEquals(checksum, 
firstRoute("route-dump").getString("checksum"));
+    }
+
+    private JsonObject firstRoute(String id) {
+        JsonObject out = (JsonObject) 
console(id).call(DevConsole.MediaType.JSON);
+        return (JsonObject) ((JsonArray) out.getCollection("routes")).get(0);
+    }
+
+    private DevConsole console(String id) {
+        return 
PluginHelper.getDevConsoleResolver(context).resolveDevConsole(id);
+    }
+}
diff --git 
a/core/camel-core-engine/src/main/java/org/apache/camel/impl/DefaultDumpRoutesStrategy.java
 
b/core/camel-core-engine/src/main/java/org/apache/camel/impl/DefaultDumpRoutesStrategy.java
index ab7f14e0ebf2..2f023a1205a6 100644
--- 
a/core/camel-core-engine/src/main/java/org/apache/camel/impl/DefaultDumpRoutesStrategy.java
+++ 
b/core/camel-core-engine/src/main/java/org/apache/camel/impl/DefaultDumpRoutesStrategy.java
@@ -783,6 +783,11 @@ public class DefaultDumpRoutesStrategy extends 
ServiceSupport implements DumpRou
                 if (loc != null) {
                     jo.put("source", loc);
                 }
+                // the checksum of the route as written: a tool that read the 
routes from their source compares it with
+                // the running routes to tell which changed
+                if (def.getChecksum() != null) {
+                    jo.put("checksum", def.getChecksum());
+                }
                 List<JsonObject> code = dumpAsJSon(lines);
                 jo.put("code", code);
                 list.add(jo);
diff --git 
a/core/camel-core-model/src/main/java/org/apache/camel/model/RouteDefinition.java
 
b/core/camel-core-model/src/main/java/org/apache/camel/model/RouteDefinition.java
index 3e6d91a12072..f93bca2ec0c9 100644
--- 
a/core/camel-core-model/src/main/java/org/apache/camel/model/RouteDefinition.java
+++ 
b/core/camel-core-model/src/main/java/org/apache/camel/model/RouteDefinition.java
@@ -98,6 +98,7 @@ public class RouteDefinition extends 
OutputDefinition<RouteDefinition>
     private Map<String, Object> templateDefaultParameters;
     private RouteTemplateContext routeTemplateContext;
     private String routeTemplateId;
+    private String checksum;
     private Resource resource;
     private Resource templatedRouteResource;
     private String precondition;
@@ -909,6 +910,20 @@ public class RouteDefinition extends 
OutputDefinition<RouteDefinition>
         this.routeTemplateId = routeTemplateId;
     }
 
+    /**
+     * The checksum of the route as written, computed when the route is 
prepared, before the parts of route
+     * configurations are added to it; <tt>null</tt> when it is not known 
(camel-xml-io is not on the classpath).
+     */
+    @Override
+    public String getChecksum() {
+        return checksum;
+    }
+
+    @XmlTransient
+    public void setChecksum(String checksum) {
+        this.checksum = checksum;
+    }
+
     public Resource getResource() {
         return resource;
     }
diff --git 
a/core/camel-core-model/src/main/java/org/apache/camel/model/RouteDefinitionHelper.java
 
b/core/camel-core-model/src/main/java/org/apache/camel/model/RouteDefinitionHelper.java
index 658af1592e8d..d17c24cdbfc9 100644
--- 
a/core/camel-core-model/src/main/java/org/apache/camel/model/RouteDefinitionHelper.java
+++ 
b/core/camel-core-model/src/main/java/org/apache/camel/model/RouteDefinitionHelper.java
@@ -38,6 +38,7 @@ import org.apache.camel.NamedNode;
 import org.apache.camel.RuntimeCamelException;
 import org.apache.camel.model.rest.RestDefinition;
 import org.apache.camel.model.rest.VerbDefinition;
+import org.apache.camel.spi.ModelChecksumGenerator;
 import org.apache.camel.spi.NodeIdFactory;
 import org.apache.camel.support.CamelContextHelper;
 import org.apache.camel.support.EndpointHelper;
@@ -421,6 +422,11 @@ public final class RouteDefinitionHelper {
         // init the route inputs
         initRouteInput();
 
+        // the checksum of the route as written, before the parts of route 
configurations are merged into it
+        if (route.getChecksum() == null) {
+            route.setChecksum(checksum(context, route));
+        }
+
         // abstracts is the cross cutting concerns
         List<ProcessorDefinition<?>> abstracts = new ArrayList<>();
 
@@ -455,6 +461,24 @@ public final class RouteDefinitionHelper {
         route.getOutputs().addAll(0, upper);
     }
 
+    /**
+     * The checksum of a route as written: the same route gives the same 
checksum in every run, so tools can tell which
+     * routes changed (see {@link ModelChecksumGenerator}). A tool that reads 
a route from its source without running it
+     * gets the checksum of the running route from this method too.
+     *
+     * @param  context the camel context
+     * @param  route   the route, before it is prepared
+     * @return         the checksum, or <tt>null</tt> when it cannot be 
computed (camel-xml-io is not on the classpath)
+     */
+    public static String checksum(CamelContext context, NamedNode route) {
+        if (context == null || route == null) {
+            return null;
+        }
+        ModelChecksumGenerator generator
+                = 
context.getCamelContextExtension().getContextPlugin(ModelChecksumGenerator.class);
+        return generator != null ? generator.generateChecksum(context, route) 
: null;
+    }
+
     /**
      * Sanity check the route, that it has input(s) and outputs.
      *
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/impl/RouteChecksumTest.java 
b/core/camel-core/src/test/java/org/apache/camel/impl/RouteChecksumTest.java
new file mode 100644
index 000000000000..107d87bc0a24
--- /dev/null
+++ b/core/camel-core/src/test/java/org/apache/camel/impl/RouteChecksumTest.java
@@ -0,0 +1,271 @@
+/*
+ * 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.impl;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+import java.util.function.Consumer;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Route;
+import org.apache.camel.RoutesBuilder;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.builder.RouteConfigurationBuilder;
+import org.apache.camel.builder.TemplatedRouteBuilder;
+import org.apache.camel.spi.CamelEvent;
+import org.apache.camel.spi.CamelEvent.RouteAddedEvent;
+import org.apache.camel.support.EventNotifierSupport;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * CAMEL-25771: each route has a checksum of the route as written, the same in 
every run, and a revision that counts the
+ * times it came back changed in this run, so tools can tell which routes 
changed.
+ */
+class RouteChecksumTest {
+
+    private static RouteBuilder orders(String target) {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("direct:orders").routeId("orders")
+                        .split(body()).parallelProcessing()
+                        .to("log:line")
+                        .end()
+                        .to(target);
+            }
+        };
+    }
+
+    private static String checksum(String routeId, RoutesBuilder... builders) 
throws Exception {
+        return checksum(routeId, null, builders);
+    }
+
+    private static String checksum(String routeId, Consumer<CamelContext> 
setup, RoutesBuilder... builders)
+            throws Exception {
+        try (CamelContext context = new DefaultCamelContext()) {
+            if (setup != null) {
+                setup.accept(context);
+            }
+            for (RoutesBuilder builder : builders) {
+                context.addRoutes(builder);
+            }
+            context.start();
+            Route route = context.getRoute(routeId);
+            assertNotNull(route, routeId);
+            return route.getChecksum();
+        }
+    }
+
+    @Test
+    void testSameRouteSameChecksumInEveryRun() throws Exception {
+        String first = checksum("orders", orders("mock:out"));
+        String second = checksum("orders", orders("mock:out"));
+
+        assertNotNull(first);
+        assertTrue(first.matches("[0-9a-f]{12}"), first);
+        assertEquals(first, second);
+    }
+
+    @Test
+    void testEditedRouteNewChecksum() throws Exception {
+        String before = checksum("orders", orders("mock:out"));
+
+        assertNotEquals(before, checksum("orders", orders("mock:other")));
+        // an option of an EIP is part of the route too
+        String sequential = checksum("orders", new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("direct:orders").routeId("orders")
+                        .split(body())
+                        .to("log:line")
+                        .end()
+                        .to("mock:out");
+            }
+        });
+        assertNotEquals(before, sequential);
+    }
+
+    @Test
+    void testGeneratedIdsAndOtherRoutesLeftOut() throws Exception {
+        String alone = checksum("orders", orders("mock:out"));
+        // another route first: the ids Camel generates for the steps of 
orders are not the same
+        String after = checksum("orders", new RouteBuilder() {
+            @Override
+            public void configure() {
+                
from("direct:audit").to("log:audit").split(body()).to("log:a").end();
+            }
+        }, orders("mock:out"));
+
+        assertEquals(alone, after);
+    }
+
+    @Test
+    void testRouteConfigurationsLeftOut() throws Exception {
+        String plain = checksum("orders", orders("mock:out"));
+        // the onException of a route configuration is merged into every 
route, which are not changed by it
+        String configured = checksum("orders", new RouteConfigurationBuilder() 
{
+            @Override
+            public void configuration() {
+                
routeConfiguration().onException(Exception.class).handled(true).to("log:failed");
+            }
+        }, orders("mock:out"));
+        assertEquals(plain, configured);
+
+        // one the route has itself is part of it
+        String own = checksum("orders", new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("direct:orders").routeId("orders")
+                        
.onException(Exception.class).handled(true).to("log:failed").end()
+                        .split(body()).parallelProcessing()
+                        .to("log:line")
+                        .end()
+                        .to("mock:out");
+            }
+        });
+        assertNotEquals(plain, own);
+    }
+
+    @Test
+    void testPlaceholdersAsWritten() throws Exception {
+        String a = checksum("orders", context -> 
context.getPropertiesComponent().setInitialProperties(props("mock:a")),
+                orders("{{target}}"));
+        String b = checksum("orders", context -> 
context.getPropertiesComponent().setInitialProperties(props("mock:b")),
+                orders("{{target}}"));
+
+        // another value is configuration, not another route
+        assertEquals(a, b);
+    }
+
+    @Test
+    void testSourceLocationsLeftOutAlsoWhileDebugging() throws Exception {
+        String plain = checksum("orders", orders("mock:out"));
+        String debugging = checksum("orders", context -> {
+            context.setSourceLocationEnabled(true);
+            context.setDebugging(true);
+        }, orders("mock:out"));
+
+        assertEquals(plain, debugging);
+    }
+
+    @Test
+    void testRevisionCountsTheChanges() throws Exception {
+        try (CamelContext context = new DefaultCamelContext()) {
+            context.addRoutes(orders("mock:out"));
+            context.start();
+            assertEquals(1, context.getRoute("orders").getRevision());
+
+            // removed and added again as it was: the same revision
+            replace(context, orders("mock:out"));
+            assertEquals(1, context.getRoute("orders").getRevision());
+
+            // changed: the next revision, which it keeps when it comes back 
the same
+            replace(context, orders("mock:other"));
+            assertEquals(2, context.getRoute("orders").getRevision());
+            replace(context, orders("mock:other"));
+            assertEquals(2, context.getRoute("orders").getRevision());
+
+            // changed back: still a change in this run
+            replace(context, orders("mock:out"));
+            assertEquals(3, context.getRoute("orders").getRevision());
+        }
+    }
+
+    @Test
+    void testTemplatedRouteSameChecksumWhenCreatedAgain() throws Exception {
+        try (CamelContext context = new DefaultCamelContext()) {
+            context.addRoutes(new RouteBuilder() {
+                @Override
+                public void configure() {
+                    
routeTemplate("orders-template").templateParameter("target")
+                            .from("direct:orders")
+                            .split(body())
+                            .to("{{target}}");
+                }
+            });
+            context.start();
+
+            String first = addOrders(context);
+            assertEquals(1, context.getRoute("orders").getRevision());
+
+            // created again, as a reload does: the node prefix comes from a 
counter, which is no part of the checksum
+            context.getRouteController().stopRoute("orders");
+            context.removeRoute("orders");
+            String second = addOrders(context);
+
+            assertNotNull(first);
+            assertEquals(first, second);
+            assertEquals(1, context.getRoute("orders").getRevision());
+        }
+    }
+
+    private static String addOrders(CamelContext context) {
+        TemplatedRouteBuilder.builder(context, 
"orders-template").routeId("orders").parameter("target", "mock:out")
+                .add();
+        return context.getRoute("orders").getChecksum();
+    }
+
+    @Test
+    void testSecretsInUrisMasked() throws Exception {
+        // the checksum is printed beside the masked URIs: it must not tell 
one literal password from another
+        String a = checksum("orders", 
orders("stub:http://localhost/orders?password=secret1";));
+        String b = checksum("orders", 
orders("stub:http://localhost/orders?password=secret2";));
+        String other = checksum("orders", 
orders("stub:http://localhost/other?password=secret1";));
+
+        assertEquals(a, b);
+        assertNotEquals(a, other);
+    }
+
+    @Test
+    void testRevisionKnownWhenTheRouteIsAnnounced() throws Exception {
+        List<Integer> revisions = new ArrayList<>();
+        try (CamelContext context = new DefaultCamelContext()) {
+            context.getManagementStrategy().addEventNotifier(new 
EventNotifierSupport() {
+                @Override
+                public void notify(CamelEvent event) {
+                    if (event instanceof RouteAddedEvent added) {
+                        revisions.add(added.getRoute().getRevision());
+                    }
+                }
+            });
+            context.addRoutes(orders("mock:out"));
+            context.start();
+            replace(context, orders("mock:other"));
+        }
+
+        // 0 would say the checksum is not known
+        assertEquals(List.of(1, 2), revisions);
+    }
+
+    private static void replace(CamelContext context, RoutesBuilder builder) 
throws Exception {
+        context.getRouteController().stopRoute("orders");
+        context.removeRoute("orders");
+        context.addRoutes(builder);
+    }
+
+    private static Properties props(String target) {
+        Properties answer = new Properties();
+        answer.put("target", target);
+        return answer;
+    }
+}
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/support/RouteWatcherReloadChangedRoutesTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/support/RouteWatcherReloadChangedRoutesTest.java
new file mode 100644
index 000000000000..ce3dd8d7fb9d
--- /dev/null
+++ 
b/core/camel-core/src/test/java/org/apache/camel/support/RouteWatcherReloadChangedRoutesTest.java
@@ -0,0 +1,161 @@
+/*
+ * 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.support;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.Route;
+import org.apache.camel.RoutesBuilder;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.impl.engine.DefaultRoutesLoader;
+import org.apache.camel.spi.Resource;
+import org.apache.camel.spi.RoutesLoader;
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.core.Appender;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.appender.AbstractAppender;
+import org.apache.logging.log4j.core.config.Property;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * CAMEL-25771: the summary of a reload says how many routes the reload 
changed, and marks them: a route that came back
+ * with another checksum is changed, one that was not there before is new.
+ */
+class RouteWatcherReloadChangedRoutesTest extends ContextTestSupport {
+
+    @TempDir
+    Path dir;
+
+    private final List<String> lines = new CopyOnWriteArrayList<>();
+    private Appender appender;
+    private NextRoutesLoader loader;
+    private RouteWatcherReloadStrategy strategy;
+
+    private static RouteBuilder routes(String ordersTarget, boolean audit) {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("direct:orders").routeId("orders").to(ordersTarget);
+                from("direct:stock").routeId("stock").to("mock:stock");
+                if (audit) {
+                    from("direct:audit").routeId("audit").to("mock:audit");
+                }
+            }
+        };
+    }
+
+    @Override
+    protected RoutesBuilder createRouteBuilder() {
+        return routes("mock:out", false);
+    }
+
+    @Override
+    @BeforeEach
+    public void setUp() throws Exception {
+        super.setUp();
+        loader = new NextRoutesLoader();
+        loader.setCamelContext(context);
+        
context.getCamelContextExtension().addContextPlugin(RoutesLoader.class, loader);
+        strategy = new RouteWatcherReloadStrategy();
+        strategy.setCamelContext(context);
+        strategy.start();
+
+        appender = new AbstractAppender("reload-summary", null, null, true, 
Property.EMPTY_ARRAY) {
+            @Override
+            public void append(LogEvent event) {
+                if 
(RouteWatcherReloadStrategy.class.getName().equals(event.getLoggerName())) {
+                    lines.add(event.getMessage().getFormattedMessage());
+                }
+            }
+        };
+        appender.start();
+        LoggerContext logging = (LoggerContext) LogManager.getContext(false);
+        logging.getConfiguration().getRootLogger().addAppender(appender, 
Level.ALL, null);
+        logging.updateLoggers();
+    }
+
+    @AfterEach
+    void stopCapture() {
+        strategy.stop();
+        LoggerContext logging = (LoggerContext) LogManager.getContext(false);
+        
logging.getConfiguration().getRootLogger().removeAppender(appender.getName());
+        logging.updateLoggers();
+        appender.stop();
+    }
+
+    @Test
+    void theSummarySaysWhichRoutesTheReloadChanged() throws Exception {
+        // the file now sends the orders elsewhere and has a new route; the 
stock route is as it was
+        loader.next = routes("mock:other", true);
+
+        strategy.onRouteReload(List.of(resource()), false);
+
+        assertTrue(lines.contains("Routes reloaded summary (total:3 started:3 
changed:2)"), lines.toString());
+        assertTrue(line("orders").endsWith("(changed)"), line("orders"));
+        assertTrue(line("audit").endsWith("(new)"), line("audit"));
+        assertTrue(line("stock").endsWith(")") && 
!line("stock").contains("changed"), line("stock"));
+        assertEquals(2, context.getRoute("orders").getRevision());
+        assertEquals(1, context.getRoute("stock").getRevision());
+
+        // saved again as it is: nothing changed
+        lines.clear();
+        strategy.onRouteReload(List.of(resource()), false);
+        assertTrue(lines.contains("Routes reloaded summary (total:3 started:3 
changed:0)"), lines.toString());
+        assertEquals(2, context.getRoute("orders").getRevision());
+    }
+
+    private String line(String routeId) {
+        return lines.stream().filter(l -> l.contains(" " + routeId + " 
(")).findFirst().orElse("");
+    }
+
+    private Resource resource() throws Exception {
+        Path file = Files.writeString(dir.resolve("routes.yaml"), "- route: 
{}\n");
+        return ResourceHelper.resolveMandatoryResource(context, "file:" + 
file);
+    }
+
+    // loads the next version of the routes, whatever the file has
+    private static class NextRoutesLoader extends DefaultRoutesLoader {
+        private RoutesBuilder next;
+
+        @Override
+        public Set<String> updateRoutes(Collection<Resource> resources) throws 
Exception {
+            for (Route route : List.copyOf(getCamelContext().getRoutes())) {
+                
getCamelContext().getRouteController().stopRoute(route.getId());
+                getCamelContext().removeRoute(route.getId());
+            }
+            getCamelContext().addRoutes(next);
+            Set<String> ids = new LinkedHashSet<>();
+            getCamelContext().getRoutes().forEach(r -> ids.add(r.getId()));
+            return ids;
+        }
+    }
+}
diff --git 
a/core/camel-management-api/src/main/java/org/apache/camel/api/management/mbean/ManagedRouteMBean.java
 
b/core/camel-management-api/src/main/java/org/apache/camel/api/management/mbean/ManagedRouteMBean.java
index d16ac9e8043f..a2d9cb08e47d 100644
--- 
a/core/camel-management-api/src/main/java/org/apache/camel/api/management/mbean/ManagedRouteMBean.java
+++ 
b/core/camel-management-api/src/main/java/org/apache/camel/api/management/mbean/ManagedRouteMBean.java
@@ -58,6 +58,12 @@ public interface ManagedRouteMBean extends 
ManagedPerformanceCounterMBean {
     @ManagedAttribute(description = "Route Source Location (Short)")
     String getSourceLocationShort();
 
+    @ManagedAttribute(description = "The checksum of the route as written, the 
same in every run: compare it to tell whether the route changed (null when 
camel-xml-io is not on the classpath)")
+    String getChecksum();
+
+    @ManagedAttribute(description = "The revision of the route in this run: 1 
when it is added, one more each time it is added again with another checksum, 
such as by a reload (0 when the checksum is not known)")
+    int getRevision();
+
     @ManagedAttribute(description = "The number of steps that have been 
reached with more than one body type, across the messages (recorded while the 
message history is on)")
     int getVariedBodyTypes();
 
diff --git 
a/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedRoute.java
 
b/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedRoute.java
index ccf087f26c87..cff094fc7858 100644
--- 
a/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedRoute.java
+++ 
b/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedRoute.java
@@ -205,6 +205,16 @@ public class ManagedRoute extends 
ManagedPerformanceCounter implements ManagedRo
         return sourceLocationShort;
     }
 
+    @Override
+    public String getChecksum() {
+        return route.getChecksum();
+    }
+
+    @Override
+    public int getRevision() {
+        return route.getRevision();
+    }
+
     @Override
     public int getVariedBodyTypes() {
         BodyTypeRegistry registry = 
context.getCamelContextExtension().getContextPlugin(BodyTypeRegistry.class);
diff --git 
a/core/camel-management/src/test/java/org/apache/camel/management/ManagedRouteChecksumTest.java
 
b/core/camel-management/src/test/java/org/apache/camel/management/ManagedRouteChecksumTest.java
new file mode 100644
index 000000000000..a3b0d87ceada
--- /dev/null
+++ 
b/core/camel-management/src/test/java/org/apache/camel/management/ManagedRouteChecksumTest.java
@@ -0,0 +1,59 @@
+/*
+ * 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 javax.management.MBeanServer;
+import javax.management.ObjectName;
+
+import org.apache.camel.builder.RouteBuilder;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.DisabledOnOs;
+import org.junit.jupiter.api.condition.OS;
+
+import static 
org.apache.camel.management.DefaultManagementObjectNameStrategy.TYPE_ROUTE;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * CAMEL-25771: a managed route has the checksum of the route as written, and 
its revision in this run.
+ */
+@DisabledOnOs(OS.AIX)
+class ManagedRouteChecksumTest extends ManagementTestSupport {
+
+    @Test
+    void testChecksumAndRevision() throws Exception {
+        MBeanServer mbeanServer = getMBeanServer();
+        ObjectName on = getCamelObjectName(TYPE_ROUTE, "myRoute");
+
+        String checksum = (String) mbeanServer.getAttribute(on, "Checksum");
+        assertNotNull(checksum);
+        assertTrue(checksum.matches("[0-9a-f]{12}"), checksum);
+        assertEquals(context.getRoute("myRoute").getChecksum(), checksum);
+        assertEquals(1, mbeanServer.getAttribute(on, "Revision"));
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("direct:start").routeId("myRoute").to("mock:result");
+            }
+        };
+    }
+}
diff --git 
a/core/camel-support/src/main/java/org/apache/camel/support/RouteWatcherReloadStrategy.java
 
b/core/camel-support/src/main/java/org/apache/camel/support/RouteWatcherReloadStrategy.java
index eb2027a783f4..1d97fec5da47 100644
--- 
a/core/camel-support/src/main/java/org/apache/camel/support/RouteWatcherReloadStrategy.java
+++ 
b/core/camel-support/src/main/java/org/apache/camel/support/RouteWatcherReloadStrategy.java
@@ -27,6 +27,7 @@ import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Deque;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -578,6 +579,10 @@ public class RouteWatcherReloadStrategy extends 
FileWatcherResourceReloadStrateg
             });
         }
 
+        // the revision of each route before the reload, to tell the routes it 
changed (CAMEL-25771)
+        Map<String, Integer> revisions = new HashMap<>();
+        getCamelContext().getRoutes().forEach(r -> revisions.put(r.getId(), 
r.getRevision()));
+
         try {
             // should all existing routes be stopped and removed first?
             if (removeAllRoutes) {
@@ -666,6 +671,9 @@ public class RouteWatcherReloadStrategy extends 
FileWatcherResourceReloadStrateg
                 List<String> lines = new ArrayList<>();
                 int total = 0;
                 int started = 0;
+                // the routes the reload changed, known when the routes have 
checksums (camel-xml-io)
+                int changed = 0;
+                boolean checksums = true;
                 for (String id : ids) {
                     total++;
                     Route route = getCamelContext().getRoute(id);
@@ -686,10 +694,24 @@ public class RouteWatcherReloadStrategy extends 
FileWatcherResourceReloadStrateg
                         if (loc == null) {
                             loc = "";
                         }
-                        lines.add(String.format("    %s %s (%s) (source: %s)", 
status, id, uri, loc));
+                        String change = "";
+                        if (route.getRevision() == 0) {
+                            checksums = false;
+                        } else if (!revisions.containsKey(id)) {
+                            change = " (new)";
+                            changed++;
+                        } else if (revisions.get(id) != route.getRevision()) {
+                            change = " (changed)";
+                            changed++;
+                        }
+                        lines.add(String.format("    %s %s (%s) (source: 
%s)%s", status, id, uri, loc, change));
                     }
                 }
-                LOG.info("Routes reloaded summary (total:{} started:{})", 
total, started);
+                if (checksums) {
+                    LOG.info("Routes reloaded summary (total:{} started:{} 
changed:{})", total, started, changed);
+                } else {
+                    LOG.info("Routes reloaded summary (total:{} started:{})", 
total, started);
+                }
                 // if we are default/verbose then log each route line
                 if (getCamelContext().getStartupSummaryLevel() == 
StartupSummaryLevel.Default
                         || getCamelContext().getStartupSummaryLevel() == 
StartupSummaryLevel.Verbose) {
diff --git 
a/core/camel-xml-io/src/generated/resources/META-INF/services/org/apache/camel/model-checksum-generator
 
b/core/camel-xml-io/src/generated/resources/META-INF/services/org/apache/camel/model-checksum-generator
new file mode 100644
index 000000000000..4f7b3a5910dd
--- /dev/null
+++ 
b/core/camel-xml-io/src/generated/resources/META-INF/services/org/apache/camel/model-checksum-generator
@@ -0,0 +1,2 @@
+# Generated by camel build tools - do NOT edit this file!
+class=org.apache.camel.xml.LwModelChecksumGenerator
diff --git 
a/core/camel-xml-io/src/main/java/org/apache/camel/xml/LwModelChecksumGenerator.java
 
b/core/camel-xml-io/src/main/java/org/apache/camel/xml/LwModelChecksumGenerator.java
new file mode 100644
index 000000000000..208c3d956695
--- /dev/null
+++ 
b/core/camel-xml-io/src/main/java/org/apache/camel/xml/LwModelChecksumGenerator.java
@@ -0,0 +1,63 @@
+/*
+ * 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.xml;
+
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.nio.charset.StandardCharsets;
+import java.security.DigestOutputStream;
+import java.security.MessageDigest;
+import java.util.HexFormat;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.NamedNode;
+import org.apache.camel.spi.ModelChecksumGenerator;
+import org.apache.camel.spi.annotations.JdkService;
+import org.apache.camel.xml.out.ModelWriter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * {@link ModelChecksumGenerator} based on the generated {@link ModelWriter}, 
which writes every option of the model:
+ * the SHA-256 of the definition as XML, with property placeholders as 
written, without the ids Camel generates (also
+ * those a route template or Kamelet makes from a counter), without source 
locations (also while debugging), and with
+ * the secrets in endpoint URIs masked. The first 12 hex characters of it are 
the checksum.
+ */
+@JdkService(ModelChecksumGenerator.FACTORY)
+public class LwModelChecksumGenerator implements ModelChecksumGenerator {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(LwModelChecksumGenerator.class);
+
+    /** The bytes of the digest the checksum has: 12 hex characters. */
+    private static final int CHECKSUM_BYTES = 6;
+
+    @Override
+    public String generateChecksum(CamelContext context, NamedNode definition) 
{
+        try {
+            MessageDigest digest = MessageDigest.getInstance("SHA-256");
+            try (Writer out = new OutputStreamWriter(
+                    new DigestOutputStream(OutputStream.nullOutputStream(), 
digest), StandardCharsets.UTF_8)) {
+                LwModelToXMLDumper.write(context, definition, out, false, 
false, false, true);
+            }
+            return HexFormat.of().formatHex(digest.digest(), 0, 
CHECKSUM_BYTES);
+        } catch (Exception e) {
+            LOG.debug("Cannot compute the checksum of {} due to {}", 
definition, e.getMessage(), e);
+            return null;
+        }
+    }
+}
diff --git 
a/core/camel-xml-io/src/main/java/org/apache/camel/xml/LwModelToXMLDumper.java 
b/core/camel-xml-io/src/main/java/org/apache/camel/xml/LwModelToXMLDumper.java
index 915e4cceb4a1..8dc9459b7c95 100644
--- 
a/core/camel-xml-io/src/main/java/org/apache/camel/xml/LwModelToXMLDumper.java
+++ 
b/core/camel-xml-io/src/main/java/org/apache/camel/xml/LwModelToXMLDumper.java
@@ -18,14 +18,21 @@ package org.apache.camel.xml;
 
 import java.io.IOException;
 import java.io.StringWriter;
+import java.io.Writer;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Properties;
+import java.util.Set;
 import java.util.function.Consumer;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 import org.apache.camel.CamelContext;
 import org.apache.camel.CamelContextAware;
@@ -54,6 +61,7 @@ import org.apache.camel.spi.NamespaceAware;
 import org.apache.camel.spi.annotations.JdkService;
 import org.apache.camel.util.KeyValueHolder;
 import org.apache.camel.util.StringHelper;
+import org.apache.camel.util.URISupport;
 import org.apache.camel.xml.out.BaseWriter;
 import org.apache.camel.xml.out.ModelWriter;
 
@@ -65,6 +73,10 @@ import static 
org.apache.camel.model.ProcessorDefinitionHelper.filterTypeInOutpu
 @JdkService(ModelToXMLDumper.FACTORY)
 public class LwModelToXMLDumper implements ModelToXMLDumper {
 
+    // the separator before a query parameter, and its value; the & after it 
is not part of the match
+    private static final Pattern HASH_PARAMETER = 
Pattern.compile("([?&])hash=([0-9a-f]+)(?=&|$)");
+    private static final Pattern ROUTE_ID_PARAMETER = 
Pattern.compile("([?&])routeId=([^&]*)(?=&|$)");
+
     @Override
     public String dumpModelAsXml(CamelContext context, NamedNode definition) 
throws Exception {
         return dumpModelAsXml(context, definition, false, true, false);
@@ -75,17 +87,42 @@ public class LwModelToXMLDumper implements ModelToXMLDumper 
{
             CamelContext context, NamedNode definition, boolean 
resolvePlaceholders, boolean generatedIds,
             boolean sourceLocation)
             throws Exception {
+        StringWriter buffer = new StringWriter();
+        // while debugging the source locations are written too, for the 
debugger to show where a step is
+        write(context, definition, buffer, resolvePlaceholders, generatedIds, 
sourceLocation || context.isDebugging(),
+                false);
+        return buffer.toString();
+    }
+
+    /**
+     * Writes the definition as XML: the dump, and the text the checksum of a 
route is computed from
+     * ({@link LwModelChecksumGenerator}), which never has the source 
locations.
+     *
+     * @param checksum whether the text is for the checksum: then what Camel 
makes from a counter or an identity hash is
+     *                 left out (the node prefix and the route id that a route 
template or Kamelet makes, the hash of
+     *                 the endpoint DSL options that are objects), and the 
secrets in endpoint URIs are masked
+     */
+    static void write(
+            CamelContext context, NamedNode definition, Writer out, boolean 
resolvePlaceholders, boolean generatedIds,
+            boolean sourceLocation, boolean checksum)
+            throws Exception {
 
         Properties properties = new Properties();
         Map<String, String> namespaces = new LinkedHashMap<>();
         Map<String, KeyValueHolder<Integer, String>> locations = new 
HashMap<>();
         List<Runnable> restorers = new ArrayList<>();
+        // the definitions whose id Camel makes from a counter, though it is 
marked as custom, and those ids
+        Set<OptionalIdentifiedDefinition<?>> counterIds = 
Collections.newSetFromMap(new IdentityHashMap<>());
+        Set<String> counterRouteIds = new HashSet<>();
         Consumer<RouteDefinition> extractor = route -> {
             extractNamespaces(route, namespaces);
-            if (sourceLocation || context.isDebugging()) {
+            if (sourceLocation) {
                 extractSourceLocations(route, locations);
             }
-            restorers.add(resolveEndpointDslUris(route));
+            restorers.add(resolveEndpointDslUris(route, checksum));
+            if (checksum) {
+                restorers.add(leaveOutCounters(route, counterIds, 
counterRouteIds));
+            }
             if (Boolean.TRUE.equals(route.isTemplate())) {
                 Map<String, Object> parameters = route.getTemplateParameters();
                 if (parameters != null) {
@@ -94,13 +131,12 @@ public class LwModelToXMLDumper implements 
ModelToXMLDumper {
             }
         };
 
-        StringWriter buffer = new StringWriter();
-        ModelWriter writer = new ModelWriter(buffer, 
BaseWriter.DEFAULT_NAMESPACE) {
+        ModelWriter writer = new ModelWriter(out, 
BaseWriter.DEFAULT_NAMESPACE) {
             @Override
             protected void 
doWriteOptionalIdentifiedDefinitionAttributes(OptionalIdentifiedDefinition<?> 
def)
                     throws IOException {
 
-                if (generatedIds || Boolean.TRUE.equals(def.getCustomId())) {
+                if ((generatedIds || Boolean.TRUE.equals(def.getCustomId())) 
&& !counterIds.contains(def)) {
                     // write id
                     doWriteAttribute("id", def.getId());
                 }
@@ -113,7 +149,7 @@ public class LwModelToXMLDumper implements ModelToXMLDumper 
{
                     doWriteAttribute("note", def.getNote());
                 }
                 // write location information
-                if (sourceLocation || context.isDebugging()) {
+                if (sourceLocation) {
                     int line = (def instanceof RouteDefinition ? 
((RouteDefinition) def).getInput() : def).getLineNumber();
                     if (line != -1) {
                         writer.addAttribute("sourceLineNumber", 
Integer.toString(line));
@@ -181,6 +217,15 @@ public class LwModelToXMLDumper implements 
ModelToXMLDumper {
                 if (resolvePlaceholders && value != null) {
                     value = resolve(value.toString(), properties);
                 }
+                if (checksum && "uri".equals(name) && value != null) {
+                    String uri = value.toString();
+                    for (String id : counterRouteIds) {
+                        // kamelet:source and kamelet:sink have the id of 
their route
+                        uri = withoutRouteId(uri, id);
+                    }
+                    // a literal password is no part of the checksum, which is 
printed beside the masked URIs
+                    value = URISupport.sanitizeUri(uri);
+                }
                 super.attribute(name, value);
             }
 
@@ -212,8 +257,6 @@ public class LwModelToXMLDumper implements ModelToXMLDumper 
{
         } finally {
             restorers.forEach(Runnable::run);
         }
-
-        return buffer.toString();
     }
 
     @Override
@@ -320,25 +363,78 @@ public class LwModelToXMLDumper implements 
ModelToXMLDumper {
         }
     }
 
+    /**
+     * The URI of an endpoint DSL builder, for the checksum without the hash 
of its options that are objects: an
+     * identity hash, another one in every run. The objects are beans, which 
the checksum leaves out.
+     */
+    static String rawUri(String uri, boolean checksum) {
+        return checksum && uri != null ? withoutParameter(uri, HASH_PARAMETER, 
null) : uri;
+    }
+
+    /** The URI without its routeId parameter of that id, which kamelet:source 
and kamelet:sink have. */
+    static String withoutRouteId(String uri, String id) {
+        return withoutParameter(uri, ROUTE_ID_PARAMETER, id);
+    }
+
+    /**
+     * The URI without the first query parameter the pattern matches, whose 
value is the given value, or any value when
+     * it is null.
+     */
+    static String withoutParameter(String uri, Pattern parameter, String 
value) {
+        Matcher matcher = parameter.matcher(uri);
+        while (matcher.find()) {
+            if (value == null || value.equals(matcher.group(2))) {
+                if (matcher.end() < uri.length()) {
+                    // more options follow: keep the separator before it, and 
drop the & after it
+                    return uri.substring(0, matcher.start() + 1) + 
uri.substring(matcher.end() + 1);
+                }
+                return uri.substring(0, matcher.start());
+            }
+        }
+        return uri;
+    }
+
+    /**
+     * Leaves out of the checksum what a route template or a Kamelet makes 
from a counter when it creates the route: the
+     * node prefix (RouteTemplateDefinition.asRouteDefinition and 
Kamelet.templateToRoute), and the id of a Kamelet
+     * route that has none of its own, the template id and that prefix, which 
its kamelet:source and kamelet:sink have
+     * too. The same route then has the same checksum each time it is created, 
such as when a reload creates it again.
+     */
+    private static Runnable leaveOutCounters(
+            RouteDefinition route, Set<OptionalIdentifiedDefinition<?>> 
counterIds, Set<String> counterRouteIds) {
+        if (!Boolean.TRUE.equals(route.isTemplate())) {
+            return () -> {
+            };
+        }
+        String prefix = route.getNodePrefixId();
+        if (prefix != null && Boolean.TRUE.equals(route.isKamelet()) && 
route.getId() != null
+                && route.getId().endsWith("-" + prefix)) {
+            counterIds.add(route);
+            counterRouteIds.add(route.getId());
+        }
+        route.setNodePrefixId(null);
+        return () -> route.setNodePrefixId(prefix);
+    }
+
     /**
      * If the route has been built with endpoint-dsl, then the model will not 
have uri set which then cannot be included
      * in the JAXB model dump. Returns a {@link Runnable} that restores the 
original endpoint DSL builders after the XML
      * has been written, so that the live {@link RouteDefinition} is not 
permanently mutated by the dump.
      */
     @SuppressWarnings("rawtypes")
-    private static Runnable resolveEndpointDslUris(RouteDefinition route) {
+    private static Runnable resolveEndpointDslUris(RouteDefinition route, 
boolean checksum) {
         List<Runnable> restorers = new ArrayList<>();
         FromDefinition from = route.getInput();
         if (from != null && from.getEndpointConsumerBuilder() != null) {
             EndpointConsumerBuilder builder = 
from.getEndpointConsumerBuilder();
-            from.setUri(builder.getRawUri());
+            from.setUri(rawUri(builder.getRawUri(), checksum));
             restorers.add(() -> from.setEndpointConsumerBuilder(builder));
         }
         Collection<SendDefinition> col = 
filterTypeInOutputs(route.getOutputs(), SendDefinition.class);
         for (SendDefinition<?> to : col) {
             if (to.getEndpointProducerBuilder() != null) {
                 EndpointProducerBuilder builder = 
to.getEndpointProducerBuilder();
-                to.setUri(builder.getRawUri());
+                to.setUri(rawUri(builder.getRawUri(), checksum));
                 restorers.add(() -> to.setEndpointProducerBuilder(builder));
             }
         }
@@ -346,7 +442,7 @@ public class LwModelToXMLDumper implements ModelToXMLDumper 
{
         for (ToDynamicDefinition to : col2) {
             if (to.getEndpointProducerBuilder() != null) {
                 EndpointProducerBuilder builder = 
to.getEndpointProducerBuilder();
-                to.setUri(builder.getRawUri());
+                to.setUri(rawUri(builder.getRawUri(), checksum));
                 // ToDynamicDefinition.setUri does not call clear(), so the 
builder is preserved;
                 // only the uri field needs to be reset.
                 restorers.add(() -> to.setUri(null));
diff --git 
a/core/camel-xml-io/src/test/java/org/apache/camel/xml/LwModelToXMLDumperRawUriTest.java
 
b/core/camel-xml-io/src/test/java/org/apache/camel/xml/LwModelToXMLDumperRawUriTest.java
new file mode 100644
index 000000000000..3abc70cc01b9
--- /dev/null
+++ 
b/core/camel-xml-io/src/test/java/org/apache/camel/xml/LwModelToXMLDumperRawUriTest.java
@@ -0,0 +1,52 @@
+/*
+ * 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.xml;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * CAMEL-25771: the hash an endpoint DSL builder adds for its options that are 
objects is an identity hash, another one
+ * in every run, so the checksum of a route leaves it out.
+ */
+class LwModelToXMLDumperRawUriTest {
+
+    @Test
+    void theHashOfTheObjectOptionsIsLeftOutOfTheChecksum() {
+        assertEquals("sql:select?a=1&b=2", 
LwModelToXMLDumper.rawUri("sql:select?a=1&hash=1f2e3d&b=2", true));
+        assertEquals("sql:select?b=2", 
LwModelToXMLDumper.rawUri("sql:select?hash=1f2e3d&b=2", true));
+        assertEquals("sql:select?a=1", 
LwModelToXMLDumper.rawUri("sql:select?a=1&hash=1f2e3d", true));
+        assertEquals("sql:select", 
LwModelToXMLDumper.rawUri("sql:select?hash=1f2e3d", true));
+        assertEquals("sql:select?ahash=1f2e3d", 
LwModelToXMLDumper.rawUri("sql:select?ahash=1f2e3d", true));
+    }
+
+    @Test
+    void theRouteIdOfTheRouteIsLeftOut() {
+        assertEquals("kamelet:source?routeId=other",
+                
LwModelToXMLDumper.withoutRouteId("kamelet:source?routeId=other&routeId=my-route",
 "my-route"));
+        assertEquals("kamelet:source?a=1",
+                
LwModelToXMLDumper.withoutRouteId("kamelet:source?routeId=my-route&a=1", 
"my-route"));
+        assertEquals("kamelet:source?routeId=my-route2",
+                
LwModelToXMLDumper.withoutRouteId("kamelet:source?routeId=my-route2", 
"my-route"));
+    }
+
+    @Test
+    void theDumpKeepsTheHash() {
+        assertEquals("sql:select?a=1&hash=1f2e3d", 
LwModelToXMLDumper.rawUri("sql:select?a=1&hash=1f2e3d", false));
+    }
+}
diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang-diagnostics.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-jbang-diagnostics.adoc
index 62747d7ff785..65bb5d422055 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-diagnostics.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-diagnostics.adoc
@@ -184,6 +184,19 @@ Give a source file instead of a running integration to see 
the structure without
 by the Java DSL parser, without compiling it; use `--compile` to compile it 
instead.
 `--brief` shortens each node, and `--description` shows the description of 
each node instead of its code.
 
+Each route has its xref:routes.adoc#_route_checksum_and_revision[checksum] 
under its source, from a running
+integration and from a source file alike: a route read from its file has the 
same checksum as when it runs, until it is
+changed. Compare it with `camel get route --checksum` to tell whether the 
routes that run are the ones in the file. A
+Java source file read without compiling it has the checksum only when the 
parser read all of it (else use `--compile`).
+
+[source,text]
+----
+Source: orders.camel.yaml
+Checksum: aa4535cb1725
+--------------------------------------------------------------------------------
+   4: route[orders]
+----
+
 ==== Body types
 
 When the integration runs with the message history on, such as with `camel run 
--dev`, each step also shows the body
diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang-managing.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-jbang-managing.adoc
index 7c0310bfda1d..74d1b4cc13f4 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-managing.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-managing.adoc
@@ -154,6 +154,11 @@ exchanges, and a route such as `direct:audit` has the 
remote exchanges of the ro
 `camel get processor --remote` and `camel top route --remote`, and the JSON of 
these commands has the remote counters
 as `remoteTotal`, `remoteFailed` and `remoteInflight`.
 
+Use `--checksum` to show the 
xref:routes.adoc#_route_checksum_and_revision[checksum and revision] of each 
route. The
+checksum is the same in every run while the route is not changed, so comparing 
it with an earlier run tells which routes
+changed; the revision counts the times a reload changed the route since the 
integration started. The JSON output
+(`--json`) always has them, as `checksum` and `revision`.
+
 TIP: Run `camel get --help` to see all available sub-commands.
 
 == Top status
diff --git 
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-get-route.adoc 
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-get-route.adoc
index 7e5f3770b2c4..2e5da04f11f1 100644
--- 
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-get-route.adoc
+++ 
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-get-route.adoc
@@ -19,6 +19,7 @@ camel get route [options]
 [cols="2,5,1,2",options="header"]
 |===
 | Option | Description | Default | Type
+| `--checksum` | Include the checksum and revision of each route: the checksum 
is the same in every run while the route is not changed, and the revision 
counts the times a reload changed it |  | boolean
 | `--description` | Include description in the ID column (if available) |  | 
boolean
 | `--duration` | How long to keep watching or following, in millis or as a 
duration such as 30s (default: until Enter is pressed) |  | long
 | `--error` | Shows detailed information for routes that has error status |  | 
boolean
diff --git 
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-top-route.adoc 
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-top-route.adoc
index 889a4ec5d2b0..6dd64fa4440f 100644
--- 
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-top-route.adoc
+++ 
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-top-route.adoc
@@ -19,6 +19,7 @@ camel top route [options]
 [cols="2,5,1,2",options="header"]
 |===
 | Option | Description | Default | Type
+| `--checksum` | Include the checksum and revision of each route: the checksum 
is the same in every run while the route is not changed, and the revision 
counts the times a reload changed it |  | boolean
 | `--description` | Include description in the ID column (if available) |  | 
boolean
 | `--duration` | How long to keep watching or following, in millis or as a 
duration such as 30s (default: until Enter is pressed) |  | long
 | `--error` | Shows detailed information for routes that has error status |  | 
boolean
diff --git a/docs/user-manual/modules/ROOT/pages/route-reload.adoc 
b/docs/user-manual/modules/ROOT/pages/route-reload.adoc
index 3ffcd96d5350..f1f394dc56ce 100644
--- a/docs/user-manual/modules/ROOT/pages/route-reload.adoc
+++ b/docs/user-manual/modules/ROOT/pages/route-reload.adoc
@@ -71,6 +71,22 @@ This is necessary because Apache Camel must stop the 
existing routes from runnin
 
 And adding new routes is therefore possible as they would have a new unique 
route id specified.
 
+=== Which routes changed
+
+The summary that is logged after a reload says how many of the reloaded routes 
changed, and marks each one: a route
+that came back with another 
xref:routes.adoc#_route_checksum_and_revision[checksum] is `(changed)`, and a 
route that
+was not there before is `(new)`. A route that is saved again as it was is not 
marked.
+
+[source,text]
+----
+Routes reloaded summary (total:3 started:3 changed:2)
+    Started orders (direct://orders) (source: orders.yaml:3) (changed)
+    Started stock (direct://stock) (source: orders.yaml:9)
+    Started audit (direct://audit) (source: orders.yaml:14) (new)
+----
+
+The revision of a route counts the times a reload changed it in this run.
+
 === Live reload of resource files
 
 In addition to reloading routes, when `routesReloadEnabled=true` Camel also 
disables the
diff --git a/docs/user-manual/modules/ROOT/pages/routes.adoc 
b/docs/user-manual/modules/ROOT/pages/routes.adoc
index eac3d85f8de7..799e585f1e9a 100644
--- a/docs/user-manual/modules/ROOT/pages/routes.adoc
+++ b/docs/user-manual/modules/ROOT/pages/routes.adoc
@@ -259,6 +259,45 @@ YAML::
 ----
 ====
 
+== Route Checksum and Revision
+
+Each route has a _checksum_ of the route as written, which tools such as the 
Camel CLI, an IDE or an AI agent use to
+tell which routes changed between two runs of an application, or after a 
reload. The same route gives the same checksum
+in every run.
+
+The checksum is a short hash (12 hex characters) of the whole route: its 
steps, their options and expressions, with
+property placeholders as written. It leaves out what does not change the route:
+
+* the ids Camel generates (a route or step with an id of its own keeps it in 
the checksum), also the node prefix that a
+route template or a Kamelet gives the routes it creates, and the id of a 
Kamelet route without one of its own: the same
+templated route has the same checksum each time it is created;
+* where the route is in its source: moving the route in its file, or adding a 
route above it, keeps its checksum;
+* the parts that route configurations add to every route (their `onException`, 
`intercept` and `onCompletion`): a
+change in a shared route configuration does not change every route. The 
`onException` of a route itself is part of it;
+* the secrets in endpoint URIs, such as a password written in the URI, which 
are masked as in the logs.
+
+Each route also has a _revision_ in the running application: it is 1 when the 
route is added, and one more each time
+the route is added again with another checksum, such as when 
xref:route-reload.adoc[route reloading] changed it. A
+reload that brings the route back as it was keeps its revision. The revision 
follows the route id, and starts over
+when the application restarts: to compare two runs, compare the checksums. 
Camel keeps the revisions of the 10000 route
+ids used last.
+
+The checksum and revision are on the `Route` (`getChecksum()` and 
`getRevision()`), on the route in JMX (the
+`Checksum` and `Revision` attributes), in the `route` developer console, and 
in the Camel CLI
+(`camel get route --checksum`). The `route-structure` and `route-dump` 
developer consoles have the checksum of each
+route, so the facts a tool keeps per line of a route know which version of the 
route they belong to. The Camel CLI
+gives the checksum of the routes of a source file without running it (`camel 
cmd route-structure orders.camel.yaml`):
+it is the one the route has when it runs.
+
+The checksum needs `camel-xml-io` on the classpath, which comes with 
`camel-core`, `camel-management` and the Camel
+CLI connector. Without it the routes have no checksum, and no revision.
+
+NOTE: The checksum covers the route, not the code it calls: changing a Java 
lambda in `.process(...)`, or a bean the route
+calls, does not change it. Neither does a property value the route uses, which 
is configuration, not another route, nor
+an object given to an endpoint DSL option, such as a `DataSource`, which is a 
bean. A
+route without an id of its own gets an id such as `route3`, which can move to 
another route when routes are added to or
+removed from its file: match such routes by their checksum.
+
 == More Information
 
 Check the xref:dsl.adoc[list of supported languages] that you can use for 
writing Camel routes.
diff --git 
a/dsl/camel-endpointdsl/src/test/java/org/apache/camel/builder/endpoint/EndpointDslRouteChecksumTest.java
 
b/dsl/camel-endpointdsl/src/test/java/org/apache/camel/builder/endpoint/EndpointDslRouteChecksumTest.java
new file mode 100644
index 000000000000..c4e553f4b8fd
--- /dev/null
+++ 
b/dsl/camel-endpointdsl/src/test/java/org/apache/camel/builder/endpoint/EndpointDslRouteChecksumTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.builder.endpoint;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.support.processor.DefaultExchangeFormatter;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+/**
+ * CAMEL-25771: an endpoint DSL option that is an object, such as an 
ExchangeFormatter, is a bean, which the checksum of
+ * a route leaves out: the same route has the same checksum with another 
instance.
+ */
+class EndpointDslRouteChecksumTest {
+
+    @Test
+    void anOptionThatIsAnObjectKeepsTheChecksum() throws Exception {
+        String first = checksum();
+        String second = checksum();
+
+        assertNotNull(first);
+        assertEquals(first, second);
+    }
+
+    private static String checksum() throws Exception {
+        try (CamelContext context = new DefaultCamelContext()) {
+            context.addRoutes(new EndpointRouteBuilder() {
+                @Override
+                public void configure() {
+                    from(direct("orders")).routeId("orders")
+                            .to(log("out").advanced().exchangeFormatter(new 
DefaultExchangeFormatter()));
+                }
+            });
+            context.start();
+            return context.getRoute("orders").getChecksum();
+        }
+    }
+}
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 27d9042058ec..42e987a3ac18 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
@@ -15,7 +15,7 @@
     { "name": "eval", "fullName": "eval", "description": "Evaluate Camel 
expressions and scripts", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.EvalCommand", "options": [ { "names": 
"-h,--help", "description": "Display the help and sub-commands", "javaType": 
"boolean", "type": "boolean" } ], "subcommands": [ { "name": "expression", 
"fullName": "eval expression", "description": "Evaluates Camel expression", 
"sourceClass": "org.apache.camel.dsl.jbang.core.commands.action.EvalEx [...]
     { "name": "explain", "fullName": "explain", "description": "Explain what a 
Camel route does using AI\/LLM", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.Explain", "options": [ { "names": 
"--api-key", "description": "API key for authentication. Also reads 
ANTHROPIC_API_KEY, OPENAI_API_KEY, WATSONX_APIKEY, or LLM_API_KEY env vars", 
"javaType": "java.lang.String", "type": "string" }, { "names": "--api-type", 
"description": "API type: 'ollama', 'openai' (OpenAI-compatible), ' [...]
     { "name": "export", "fullName": "export", "description": "Export to other 
runtimes (Camel Main, Spring Boot, or Quarkus)", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.Export", "options": [ { "names": 
"--build-property", "description": "Maven build properties, ex. 
--build-property=prop1=foo", "javaType": "java.util.List", "type": "array" }, { 
"names": "--camel-spring-boot-version", "description": "Camel version to use 
with Spring Boot", "javaType": "java.lang.String", "ty [...]
-    { "name": "get", "fullName": "get", "description": "Get status of Camel 
integrations", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.process.CamelStatus", "options": [ { 
"names": "--watch", "description": "Execute periodically and showing output 
fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": 
"-h,--help", "description": "Display the help and sub-commands", "javaType": 
"boolean", "type": "boolean" } ], "subcommands": [ { "name": "activity", 
"fullName": " [...]
+    { "name": "get", "fullName": "get", "description": "Get status of Camel 
integrations", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.process.CamelStatus", "options": [ { 
"names": "--watch", "description": "Execute periodically and showing output 
fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": 
"-h,--help", "description": "Display the help and sub-commands", "javaType": 
"boolean", "type": "boolean" } ], "subcommands": [ { "name": "activity", 
"fullName": " [...]
     { "name": "harden", "fullName": "harden", "description": "Suggest security 
hardening for Camel routes using AI\/LLM", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.Harden", "options": [ { "names": 
"--api-key", "description": "API key for authentication. Also reads 
OPENAI_API_KEY or LLM_API_KEY env vars", "javaType": "java.lang.String", 
"type": "string" }, { "names": "--api-type", "description": "API type: 'ollama' 
or 'openai' (OpenAI-compatible)", "defaultValue": "ollama", [...]
     { "name": "hawtio", "fullName": "hawtio", "description": "Launch Hawtio 
web console", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.process.Hawtio", "options": [ { 
"names": "--host", "description": "Hostname to bind the Hawtio web console to", 
"defaultValue": "127.0.0.1", "javaType": "java.lang.String", "type": "string" 
}, { "names": "--open-url,--openUrl", "description": "To automatic open Hawtio 
web console in the web browser (without an interactive terminal the default  
[...]
     { "name": "infra", "fullName": "infra", "description": "List and Run 
external services for testing and prototyping", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.infra.InfraCommand", "options": [ { 
"names": "--json", "description": "Output in JSON Format", "javaType": 
"boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display 
the help and sub-commands", "javaType": "boolean", "type": "boolean" } ], 
"subcommands": [ { "name": "get", "fullName": "infra  [...]
@@ -33,7 +33,7 @@
     { "name": "semantic", "fullName": "semantic", "description": "List 
semantic definitions and expert contracts", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.action.CamelSemantic", "options": [ { 
"names": "--expert", "description": "Show the operations and parameter contract 
of this expert", "javaType": "java.lang.String", "type": "string" }, { "names": 
"--json", "description": "Output a single JSON document", "javaType": 
"boolean", "type": "boolean" }, { "names": "--timeou [...]
     { "name": "shell", "fullName": "shell", "description": "Interactive Camel 
CLI shell.", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Shell", 
"options": [ { "names": "-h,--help", "description": "Display the help and 
sub-commands", "javaType": "boolean", "type": "boolean" } ] },
     { "name": "stop", "fullName": "stop", "description": "Shuts down running 
Camel integrations", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.process.StopProcess", "options": [ { 
"names": "--kill", "description": "To force killing the process (SIGKILL)", 
"javaType": "boolean", "type": "boolean" }, { "names": "--no-wait", 
"description": "Do not wait for the integration to stop", "javaType": 
"boolean", "type": "boolean" }, { "names": "--timeout", "description": "How 
long to wa [...]
-    { "name": "top", "fullName": "top", "description": "Top status of Camel 
integrations", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.process.CamelTop", "options": [ { 
"names": "--watch", "description": "Execute periodically and showing output 
fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": 
"-h,--help", "description": "Display the help and sub-commands", "javaType": 
"boolean", "type": "boolean" } ], "subcommands": [ { "name": "context", 
"fullName": "top  [...]
+    { "name": "top", "fullName": "top", "description": "Top status of Camel 
integrations", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.process.CamelTop", "options": [ { 
"names": "--watch", "description": "Execute periodically and showing output 
fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": 
"-h,--help", "description": "Display the help and sub-commands", "javaType": 
"boolean", "type": "boolean" } ], "subcommands": [ { "name": "context", 
"fullName": "top  [...]
     { "name": "trace", "fullName": "trace", "description": "Tail message 
traces from running Camel integrations", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.action.CamelTraceAction", "options": 
[ { "names": "--action", "description": "Action to start, stop, clear, list 
status, or dump traces", "defaultValue": "status", "javaType": 
"java.lang.String", "type": "string" }, { "names": "--ago", "description": "Use 
ago instead of yyyy-MM-dd HH:mm:ss in timestamp.", "javaType": "b [...]
     { "name": "transform", "fullName": "transform", "description": "Transform 
message or Camel routes", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.TransformCommand", "options": [ { 
"names": "-h,--help", "description": "Display the help and sub-commands", 
"javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": 
"dataweave", "fullName": "transform dataweave", "description": "Convert 
DataWeave scripts to DataSonnet format", "sourceClass": "org.apache.camel.dsl. 
[...]
     { "name": "update", "fullName": "update", "description": "Update Camel 
project", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.update.UpdateCommand", "options": [ { 
"names": "-h,--help", "description": "Display the help and sub-commands", 
"javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": 
"list", "fullName": "update list", "description": "List available update 
versions for Camel and its runtime variants", "sourceClass": 
"org.apache.camel.dsl.jbang.cor [...]
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/TransformJavaRoutes.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/TransformJavaRoutes.java
index 331a9fc698c4..e88334d1b733 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/TransformJavaRoutes.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/TransformJavaRoutes.java
@@ -168,6 +168,15 @@ public final class TransformJavaRoutes {
                 context.addRouteDefinitions(r.routes().getRoutes());
             }
             if ("json".equals(format)) {
+                // the checksum of each route as written, which is the one the 
route has when it runs: only when the
+                // parser read the whole source, as a part it could not read 
is missing from the routes
+                if (read.unread().isEmpty()) {
+                    for (RouteDefinition route : 
context.getRouteDefinitions()) {
+                        if (route.getChecksum() == null) {
+                            
route.setChecksum(RouteDefinitionHelper.checksum(context, route));
+                        }
+                    }
+                }
                 // the ids a start assigns: the route structure looks each 
route up by its id, and shows the step ids
                 // (a route id that is a property placeholder is kept as it 
is, as there are no properties)
                 
context.getPropertiesComponent().setIgnoreMissingProperty(true);
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 eac486c2ed0c..7a95ca448bab 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
@@ -163,6 +163,7 @@ public class CamelRouteStructureAction extends 
ActionBaseCommand {
                 Row row = new Row();
                 row.location = extractSourceName(o.getString("source"));
                 row.routeId = o.getString("routeId");
+                row.checksum = o.getString("checksum");
                 if (!rows.contains(row)) {
                     List<JsonObject> lines = o.getCollection("code");
                     if (lines != null) {
@@ -267,6 +268,10 @@ public class CamelRouteStructureAction extends 
ActionBaseCommand {
             printer().println();
             if (!raw) {
                 printer().printf("Source: %s%n", row.location);
+                if (row.checksum != null) {
+                    // the same as the checksum of the running route while the 
route is not changed
+                    printer().printf("Checksum: %s%n", row.checksum);
+                }
                 
printer().println("--------------------------------------------------------------------------------");
             }
             // the body types each step was reached with, as the running app 
has seen them, line up in a column after
@@ -369,6 +374,7 @@ public class CamelRouteStructureAction extends 
ActionBaseCommand {
     private static class Row {
         String location;
         String routeId;
+        String checksum;
         List<Code> code = new ArrayList<>();
 
         @Override
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/CamelRouteStatus.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/CamelRouteStatus.java
index 76af869d1fbd..22a41c321819 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/CamelRouteStatus.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/CamelRouteStatus.java
@@ -114,6 +114,10 @@ public class CamelRouteStatus extends ProcessWatchCommand {
                         description = "Include group column")
     boolean showGroup;
 
+    @CommandLine.Option(names = { "--checksum" },
+                        description = "Include the checksum and revision of 
each route: the checksum is the same in every run while the route is not 
changed, and the revision counts the times a reload changed it")
+    boolean checksum;
+
     public CamelRouteStatus(CamelJBangMain main) {
         super(main);
     }
@@ -149,6 +153,8 @@ public class CamelRouteStatus extends ProcessWatchCommand {
                     row.remote = bool;
                 }
                 row.source = o.getString("source");
+                row.checksum = o.getString("checksum");
+                row.revision = o.getIntegerOrDefault("revision", 0);
                 row.state = o.getString("state");
                 row.age = o.getString("uptime");
                 row.uptime = row.age != null ? 
TimeUtils.toMilliSeconds(row.age) : 0;
@@ -307,6 +313,11 @@ public class CamelRouteStatus extends ProcessWatchCommand {
                 new 
Column().header("FROM").visible(wideUri).dataAlign(HorizontalAlign.LEFT)
                         .maxWidth(fromWideW, OverflowBehaviour.NEWLINE)
                         .with(r -> r.from),
+                new 
Column().header("CHECKSUM").visible(checksum).dataAlign(HorizontalAlign.LEFT)
+                        .with(r -> r.checksum != null ? r.checksum : ""),
+                new 
Column().header("REV").visible(checksum).headerAlign(HorizontalAlign.CENTER)
+                        .dataAlign(HorizontalAlign.RIGHT)
+                        .with(r -> r.revision > 0 ? String.valueOf(r.revision) 
: ""),
                 new 
Column().header("REMOTE").visible(remoteVisible).headerAlign(HorizontalAlign.CENTER)
                         .dataAlign(HorizontalAlign.CENTER)
                         .with(this::getRemote),
@@ -337,6 +348,9 @@ public class CamelRouteStatus extends ProcessWatchCommand {
         jo.put("group", r.group);
         jo.put("from", r.from);
         jo.put("remote", r.remote);
+        // the same in every run while the route is not changed, and the times 
a reload changed it (when known)
+        jo.put("checksum", r.checksum);
+        jo.put("revision", r.revision > 0 ? r.revision : null);
         jo.put("status", getStatus(r));
         jo.put("age", r.age);
         jo.put("uptime", r.uptime);
@@ -558,6 +572,8 @@ public class CamelRouteStatus extends ProcessWatchCommand {
         String from;
         boolean remote;
         String source;
+        String checksum;
+        int revision;
         String state;
         String age;
         String coverage;
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/CamelRouteTop.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/CamelRouteTop.java
index 667ab4f19cbb..7d6f5ef9c414 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/CamelRouteTop.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/CamelRouteTop.java
@@ -63,6 +63,11 @@ public class CamelRouteTop extends CamelRouteStatus {
                         .with(this::getId),
                 new 
Column().header("FROM").dataAlign(HorizontalAlign.LEFT).maxWidth(fromW, 
OverflowBehaviour.ELLIPSIS_RIGHT)
                         .with(this::getFrom),
+                new 
Column().header("CHECKSUM").visible(checksum).dataAlign(HorizontalAlign.LEFT)
+                        .with(r -> r.checksum != null ? r.checksum : ""),
+                new 
Column().header("REV").visible(checksum).headerAlign(HorizontalAlign.CENTER)
+                        .dataAlign(HorizontalAlign.RIGHT)
+                        .with(r -> r.revision > 0 ? String.valueOf(r.revision) 
: ""),
                 new 
Column().header("REMOTE").visible(remoteVisible).headerAlign(HorizontalAlign.CENTER)
                         .dataAlign(HorizontalAlign.CENTER)
                         .with(this::getRemote),
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/RouteActionJavaSourceTest.java
 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/RouteActionJavaSourceTest.java
index 24de5f0b77b5..e9d693bc2f7b 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/RouteActionJavaSourceTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/RouteActionJavaSourceTest.java
@@ -20,12 +20,16 @@ import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.List;
 
+import org.apache.camel.CamelContext;
+import org.apache.camel.builder.RouteBuilder;
 import org.apache.camel.diagram.RouteDiagramLayoutEngine.RouteInfo;
 import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
 import org.apache.camel.dsl.jbang.core.commands.UsesWorkingDirectory;
 import org.apache.camel.dsl.jbang.core.common.CliException;
 import org.apache.camel.dsl.jbang.core.common.CliExitCode;
 import org.apache.camel.dsl.jbang.core.common.StringPrinter;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.model.ModelCamelContext;
 import org.apache.camel.util.json.JsonArray;
 import org.apache.camel.util.json.JsonObject;
 import org.apache.camel.util.json.Jsoner;
@@ -212,6 +216,58 @@ class RouteActionJavaSourceTest {
                 .contains("direct:next");
     }
 
+    @Test
+    void structureHasTheChecksumTheRouteHasWhenItRuns(@TempDir Path dir) 
throws Exception {
+        Path orders = write(dir, "Orders.java", """
+                import org.apache.camel.builder.RouteBuilder;
+
+                public class Orders extends RouteBuilder {
+                    @Override
+                    public void configure() {
+                        from("direct:orders").routeId("orders")
+                            .choice()
+                                .when(simple("${header.vip} == 
true")).to("seda:vip")
+                                .otherwise().to("seda:orders")
+                            .end()
+                            .log("${body}");
+                    }
+                }
+                """);
+        StringPrinter printer = new StringPrinter();
+        CamelRouteStructureAction command = new CamelRouteStructureAction(new 
CamelJBangMain().withPrinter(printer));
+        command.name = orders.toString();
+        command.jsonOutput = true;
+
+        assertThat(command.doCall()).isZero();
+        JsonObject structure = Jsoner.deserialize(printer.getOutput(), new 
JsonObject());
+        JsonObject route = ((JsonArray) 
structure.getCollection("routes")).getMap(0);
+        String read = route.getString("checksum");
+
+        // the same route, compiled and added to a CamelContext as when it runs
+        try (CamelContext context = new DefaultCamelContext()) {
+            context.addRoutes(new RouteBuilder() {
+                @Override
+                public void configure() {
+                    from("direct:orders").routeId("orders")
+                            .choice()
+                            .when(simple("${header.vip} == 
true")).to("seda:vip")
+                            .otherwise().to("seda:orders")
+                            .end()
+                            .log("${body}");
+                }
+            });
+            String running = ((ModelCamelContext) 
context).getRouteDefinition("orders").getChecksum();
+            assertThat(read).isNotNull().isEqualTo(running);
+        }
+
+        // the text shows it under the source
+        printer = new StringPrinter();
+        command = new CamelRouteStructureAction(new 
CamelJBangMain().withPrinter(printer));
+        command.name = orders.toString();
+        assertThat(command.doCall()).isZero();
+        assertThat(printer.getOutput()).contains("Source: Orders.java", 
"Checksum: " + read);
+    }
+
     private static Path write(Path dir, String name, String content) throws 
Exception {
         Path file = dir.resolve(name);
         Files.writeString(file, content);
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/CamelRouteStatusTest.java
 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/CamelRouteStatusTest.java
index 40baa36fd3b5..8d31ed805a84 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/CamelRouteStatusTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/CamelRouteStatusTest.java
@@ -91,6 +91,34 @@ class CamelRouteStatusTest extends ProcessCommandTestSupport 
{
         assertThat(route2.get("mean")).isNull();
     }
 
+    @Test
+    void checksumShowsTheChecksumAndRevisionOfEachRoute() throws Exception {
+        JsonObject changed = route("route1", "timer:tick", "Started", 
stats(12, 1, 2, 25));
+        changed.put("checksum", "0a1b2c3d4e5f");
+        changed.put("revision", 2);
+        writeStatusFile(TEST_PID, integration("myApp", changed,
+                route("route2", "timer:tock", "Started", stats(0, 0, 0, -1))));
+
+        assertThat(call(command())).isZero();
+        assertThat(printer.getOutput()).doesNotContain("CHECKSUM", 
"0a1b2c3d4e5f");
+
+        printer = new StringPrinter();
+        assertThat(call(command("--checksum"))).isZero();
+        String[] lines = printer.getOutput().split(System.lineSeparator());
+        assertThat(lines[0]).contains("CHECKSUM", "REV");
+        assertThat(lines[1]).contains("route1", "0a1b2c3d4e5f", " 2 ");
+
+        // the JSON has them whether asked for or not; a route of an older 
Camel has none
+        printer = new StringPrinter();
+        assertThat(call(command("--json"))).isZero();
+        JsonArray routes = Jsoner.deserialize(printer.getOutput(), (JsonArray) 
null);
+        JsonObject first = routes.getMap(0);
+        JsonObject second = routes.getMap(1);
+        assertThat(first.getString("checksum")).isEqualTo("0a1b2c3d4e5f");
+        assertThat(first.getInteger("revision")).isEqualTo(2);
+        assertThat(second.get("revision")).isNull();
+    }
+
     @Test
     void jsonWithoutRoutesIsAnEmptyArray() throws Exception {
         writeStatusFile(TEST_PID, integration("myApp"));
diff --git 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/RuntimeTools.java
 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/RuntimeTools.java
index 64f26f63cffc..55017b894a70 100644
--- 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/RuntimeTools.java
+++ 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/RuntimeTools.java
@@ -146,7 +146,10 @@ public class RuntimeTools {
     @Tool(annotations = @Tool.Annotations(readOnlyHint = true, destructiveHint 
= false, openWorldHint = false),
           description = "List Camel routes with their state, uptime, messages 
processed, last error, and throughput statistics."
                         + " A route with mixedBodyKinds has steps reached with 
bodies of more than one kind, which need"
-                        + " attention: camel_runtime_route_structure with 
varied=true shows them.")
+                        + " attention: camel_runtime_route_structure with 
varied=true shows them. The checksum of a route"
+                        + " is the same in every run while the route is not 
changed: compare it with an earlier run to"
+                        + " tell whether a route changed, and match routes 
with generated ids by it; revision counts the"
+                        + " times a reload changed the route in this run.")
     public JsonObject camel_runtime_routes(
             @ToolArg(description = NAME_OR_PID_DESC, required = false) String 
nameOrPid) {
         return delegateToRegistry("get_routes", nameOrPid, Map.of());
@@ -244,7 +247,8 @@ public class RuntimeTools {
                         + " a processor has bodyTypes: the body types it has 
been reached with, across the messages, and"
                         + " bodyKinds: null, payload, data or collection. More 
than one kind needs attention: a step that"
                         + " writes fails for data, one that reads fields fails 
for a payload. The routes tool counts them"
-                        + " per route (mixedBodyKinds).")
+                        + " per route (mixedBodyKinds). The checksum of each 
route says which version of the route the"
+                        + " facts of its lines are of.")
     public JsonObject camel_runtime_route_structure(
             @ToolArg(description = NAME_OR_PID_DESC, required = false) String 
nameOrPid,
             @ToolArg(description = "Route ID or pattern such as orders* to 
inspect (default * for all routes)",

Reply via email to