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

davsclaus pushed a commit to branch feature/CAMEL-25661-reload-interval
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 5070839b7fe1a074e36d6a67553dc3ef52d0d4ed
Author: Claus Ibsen <[email protected]>
AuthorDate: Thu Oct 15 14:30:59 2026 +0200

    CAMEL-25661: camel-jbang - AI write tools reload sooner: camel_run scans 
every 500 ms
    
    The write tools wait for dev mode to reload the file they wrote, and dev 
mode
    finds a change at its next folder scan, every 2000 ms.
    
    - camel-main: camel.main.routesReloadInterval (millis, default 2000) sets 
how
      often dev mode scans; the reload strategy shows it as a JMX attribute.
    - camel_run starts integrations in dev mode with an interval of 500 ms, as 
the
      optional key camel.main.?routesReloadInterval, so an older camel on the 
PATH
      still starts. camel run --dev for people keeps 2000 ms.
    - The write tools read the log every 100 ms for the reload and its output, 
and
      a properties reload is watched 500 ms longer for the routes reload after 
it.
    
    Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
    Claude-Session: https://claude.ai/code/session_01STT6whBgK1AqsSsUKrnE8m
---
 .../org/apache/camel/catalog/docs/main.adoc        |  3 +-
 .../main/camel-main-configuration-metadata.json    |  1 +
 .../MainConfigurationPropertiesConfigurer.java     |  7 ++
 .../camel-main-configuration-metadata.json         |  1 +
 core/camel-main/src/main/docs/main.adoc            |  3 +-
 .../camel/main/DefaultConfigurationConfigurer.java |  3 +
 .../camel/main/DefaultConfigurationProperties.java | 25 ++++++++
 .../camel/main/MainRoutesReloadIntervalTest.java   | 74 ++++++++++++++++++++++
 .../support/FileWatcherResourceReloadStrategy.java |  5 ++
 .../modules/ROOT/pages/camel-jbang-mcp.adoc        |  5 +-
 .../modules/ROOT/pages/camel-jbang-running.adoc    | 11 ++++
 .../modules/ROOT/pages/route-reload.adoc           |  1 +
 .../core/commands/ai/IntegrationLauncher.java      | 12 ++++
 .../dsl/jbang/core/commands/ai/ReloadOutcome.java  | 40 ++++++++++--
 .../core/commands/ai/IntegrationLauncherTest.java  | 10 +--
 .../jbang/core/commands/ai/ReloadOutcomeTest.java  | 28 +++++++-
 16 files changed, 214 insertions(+), 15 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/main.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/main.adoc
index 3412a9aad3c3..a780e2eabb67 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/main.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/main.adoc
@@ -19,7 +19,7 @@ The following tables lists all the options:
 
 // main options: START
 === Camel Main configurations
-The camel.main supports 131 options, which are listed below.
+The camel.main supports 132 options, which are listed below.
 
 [width="100%",cols="2,5,^1,2",options="header"]
 |===
@@ -111,6 +111,7 @@ The camel.main supports 131 options, which are listed below.
 | `camel.main.routesReloadDirectory` | Directory to scan for route changes. 
Camel cannot scan the classpath, so this must be configured to a file 
directory. Development with Maven as build tool, you can configure the 
directory to be src/main/resources to scan for Camel routes in XML or YAML 
files. | src/main/resources/camel | String
 | `camel.main.routesReloadDirectoryRecursive` | Whether the directory to scan 
should include sub directories. Depending on the number of sub directories, 
then this can cause the JVM to startup slower as Camel uses the JDK file-watch 
service to scan for file changes. | false | boolean
 | `camel.main.routesReloadEnabled` | Used for enabling automatic routes 
reloading. If enabled then Camel will watch for file changes in the given 
reload directory, and trigger reloading routes if files are changed. | false | 
boolean
+| `camel.main.routesReloadInterval` | How often to scan the reload directory 
for changed files, in millis. A change is reloaded at the next scan, so a 
shorter interval reloads it sooner. A longer interval groups more of one save 
together: two files saved a second apart are one change at 2000 and two at 
1000. | 2000 | long
 | `camel.main.routesReloadPattern` | Used for inclusive filtering of routes 
from directories. Typical used for specifying to accept routes in XML or YAML 
files, such as .yaml,.xml. Multiple patterns can be specified separated by 
comma. |  | String
 | `camel.main.routesReloadRemoveAllRoutes` | When reloading routes should all 
existing routes be stopped and removed. By default, Camel will stop and remove 
all existing routes before reloading routes. This ensures that only the 
reloaded routes will be active. If disabled then only routes with the same 
route id is updated, and any existing routes are continued to run. | true | 
boolean
 | `camel.main.routesReloadRestartDuration` | Whether to restart max duration 
when routes are reloaded. For example if max duration is 60 seconds, and a 
route is reloaded after 25 seconds, then this will restart the count and wait 
60 seconds again. | false | boolean
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json
index 2de54506bdbe..cd2101ee33b7 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json
@@ -125,6 +125,7 @@
     { "name": "camel.main.routesReloadDirectory", "required": false, 
"description": "Directory to scan for route changes. Camel cannot scan the 
classpath, so this must be configured to a file directory. Development with 
Maven as build tool, you can configure the directory to be src\/main\/resources 
to scan for Camel routes in XML or YAML files.", "sourceType": 
"org.apache.camel.main.DefaultConfigurationProperties", "type": "string", 
"javaType": "java.lang.String", "defaultValue": "src\/m [...]
     { "name": "camel.main.routesReloadDirectoryRecursive", "required": false, 
"description": "Whether the directory to scan should include sub directories. 
Depending on the number of sub directories, then this can cause the JVM to 
startup slower as Camel uses the JDK file-watch service to scan for file 
changes.", "sourceType": 
"org.apache.camel.main.DefaultConfigurationProperties", "type": "boolean", 
"javaType": "boolean", "defaultValue": false, "secret": false },
     { "name": "camel.main.routesReloadEnabled", "required": false, 
"description": "Used for enabling automatic routes reloading. If enabled then 
Camel will watch for file changes in the given reload directory, and trigger 
reloading routes if files are changed.", "sourceType": 
"org.apache.camel.main.DefaultConfigurationProperties", "type": "boolean", 
"javaType": "boolean", "defaultValue": false, "secret": false },
+    { "name": "camel.main.routesReloadInterval", "required": false, 
"description": "How often to scan the reload directory for changed files, in 
millis. A change is reloaded at the next scan, so a shorter interval reloads it 
sooner. A longer interval groups more of one save together: two files saved a 
second apart are one change at 2000 and two at 1000.", "sourceType": 
"org.apache.camel.main.DefaultConfigurationProperties", "type": "integer", 
"javaType": "long", "defaultValue": 2000, "se [...]
     { "name": "camel.main.routesReloadPattern", "required": false, 
"description": "Used for inclusive filtering of routes from directories. 
Typical used for specifying to accept routes in XML or YAML files, such as 
.yaml,.xml. Multiple patterns can be specified separated by comma.", 
"sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": 
"string", "javaType": "java.lang.String", "secret": false },
     { "name": "camel.main.routesReloadRemoveAllRoutes", "required": false, 
"description": "When reloading routes should all existing routes be stopped and 
removed. By default, Camel will stop and remove all existing routes before 
reloading routes. This ensures that only the reloaded routes will be active. If 
disabled then only routes with the same route id is updated, and any existing 
routes are continued to run.", "sourceType": 
"org.apache.camel.main.DefaultConfigurationProperties", "ty [...]
     { "name": "camel.main.routesReloadRestartDuration", "required": false, 
"description": "Whether to restart max duration when routes are reloaded. For 
example if max duration is 60 seconds, and a route is reloaded after 25 
seconds, then this will restart the count and wait 60 seconds again.", 
"sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": 
"boolean", "javaType": "boolean", "defaultValue": false, "secret": false },
diff --git 
a/core/camel-main/src/generated/java/org/apache/camel/main/MainConfigurationPropertiesConfigurer.java
 
b/core/camel-main/src/generated/java/org/apache/camel/main/MainConfigurationPropertiesConfigurer.java
index c4757a38ff27..46ae4a90a907 100644
--- 
a/core/camel-main/src/generated/java/org/apache/camel/main/MainConfigurationPropertiesConfigurer.java
+++ 
b/core/camel-main/src/generated/java/org/apache/camel/main/MainConfigurationPropertiesConfigurer.java
@@ -109,6 +109,7 @@ public class MainConfigurationPropertiesConfigurer extends 
org.apache.camel.supp
         map.put("RoutesReloadDirectory", java.lang.String.class);
         map.put("RoutesReloadDirectoryRecursive", boolean.class);
         map.put("RoutesReloadEnabled", boolean.class);
+        map.put("RoutesReloadInterval", long.class);
         map.put("RoutesReloadPattern", java.lang.String.class);
         map.put("RoutesReloadRemoveAllRoutes", boolean.class);
         map.put("RoutesReloadRestartDuration", boolean.class);
@@ -328,6 +329,8 @@ public class MainConfigurationPropertiesConfigurer extends 
org.apache.camel.supp
         case "routesReloadDirectoryRecursive": 
target.setRoutesReloadDirectoryRecursive(property(camelContext, boolean.class, 
value)); return true;
         case "routesreloadenabled":
         case "routesReloadEnabled": 
target.setRoutesReloadEnabled(property(camelContext, boolean.class, value)); 
return true;
+        case "routesreloadinterval":
+        case "routesReloadInterval": 
target.setRoutesReloadInterval(property(camelContext, long.class, value)); 
return true;
         case "routesreloadpattern":
         case "routesReloadPattern": 
target.setRoutesReloadPattern(property(camelContext, java.lang.String.class, 
value)); return true;
         case "routesreloadremoveallroutes":
@@ -595,6 +598,8 @@ public class MainConfigurationPropertiesConfigurer extends 
org.apache.camel.supp
         case "routesReloadDirectoryRecursive": return boolean.class;
         case "routesreloadenabled":
         case "routesReloadEnabled": return boolean.class;
+        case "routesreloadinterval":
+        case "routesReloadInterval": return long.class;
         case "routesreloadpattern":
         case "routesReloadPattern": return java.lang.String.class;
         case "routesreloadremoveallroutes":
@@ -858,6 +863,8 @@ public class MainConfigurationPropertiesConfigurer extends 
org.apache.camel.supp
         case "routesReloadDirectoryRecursive": return 
target.isRoutesReloadDirectoryRecursive();
         case "routesreloadenabled":
         case "routesReloadEnabled": return target.isRoutesReloadEnabled();
+        case "routesreloadinterval":
+        case "routesReloadInterval": return target.getRoutesReloadInterval();
         case "routesreloadpattern":
         case "routesReloadPattern": return target.getRoutesReloadPattern();
         case "routesreloadremoveallroutes":
diff --git 
a/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
 
b/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
index 2de54506bdbe..cd2101ee33b7 100644
--- 
a/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
+++ 
b/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
@@ -125,6 +125,7 @@
     { "name": "camel.main.routesReloadDirectory", "required": false, 
"description": "Directory to scan for route changes. Camel cannot scan the 
classpath, so this must be configured to a file directory. Development with 
Maven as build tool, you can configure the directory to be src\/main\/resources 
to scan for Camel routes in XML or YAML files.", "sourceType": 
"org.apache.camel.main.DefaultConfigurationProperties", "type": "string", 
"javaType": "java.lang.String", "defaultValue": "src\/m [...]
     { "name": "camel.main.routesReloadDirectoryRecursive", "required": false, 
"description": "Whether the directory to scan should include sub directories. 
Depending on the number of sub directories, then this can cause the JVM to 
startup slower as Camel uses the JDK file-watch service to scan for file 
changes.", "sourceType": 
"org.apache.camel.main.DefaultConfigurationProperties", "type": "boolean", 
"javaType": "boolean", "defaultValue": false, "secret": false },
     { "name": "camel.main.routesReloadEnabled", "required": false, 
"description": "Used for enabling automatic routes reloading. If enabled then 
Camel will watch for file changes in the given reload directory, and trigger 
reloading routes if files are changed.", "sourceType": 
"org.apache.camel.main.DefaultConfigurationProperties", "type": "boolean", 
"javaType": "boolean", "defaultValue": false, "secret": false },
+    { "name": "camel.main.routesReloadInterval", "required": false, 
"description": "How often to scan the reload directory for changed files, in 
millis. A change is reloaded at the next scan, so a shorter interval reloads it 
sooner. A longer interval groups more of one save together: two files saved a 
second apart are one change at 2000 and two at 1000.", "sourceType": 
"org.apache.camel.main.DefaultConfigurationProperties", "type": "integer", 
"javaType": "long", "defaultValue": 2000, "se [...]
     { "name": "camel.main.routesReloadPattern", "required": false, 
"description": "Used for inclusive filtering of routes from directories. 
Typical used for specifying to accept routes in XML or YAML files, such as 
.yaml,.xml. Multiple patterns can be specified separated by comma.", 
"sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": 
"string", "javaType": "java.lang.String", "secret": false },
     { "name": "camel.main.routesReloadRemoveAllRoutes", "required": false, 
"description": "When reloading routes should all existing routes be stopped and 
removed. By default, Camel will stop and remove all existing routes before 
reloading routes. This ensures that only the reloaded routes will be active. If 
disabled then only routes with the same route id is updated, and any existing 
routes are continued to run.", "sourceType": 
"org.apache.camel.main.DefaultConfigurationProperties", "ty [...]
     { "name": "camel.main.routesReloadRestartDuration", "required": false, 
"description": "Whether to restart max duration when routes are reloaded. For 
example if max duration is 60 seconds, and a route is reloaded after 25 
seconds, then this will restart the count and wait 60 seconds again.", 
"sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": 
"boolean", "javaType": "boolean", "defaultValue": false, "secret": false },
diff --git a/core/camel-main/src/main/docs/main.adoc 
b/core/camel-main/src/main/docs/main.adoc
index 3412a9aad3c3..a780e2eabb67 100644
--- a/core/camel-main/src/main/docs/main.adoc
+++ b/core/camel-main/src/main/docs/main.adoc
@@ -19,7 +19,7 @@ The following tables lists all the options:
 
 // main options: START
 === Camel Main configurations
-The camel.main supports 131 options, which are listed below.
+The camel.main supports 132 options, which are listed below.
 
 [width="100%",cols="2,5,^1,2",options="header"]
 |===
@@ -111,6 +111,7 @@ The camel.main supports 131 options, which are listed below.
 | `camel.main.routesReloadDirectory` | Directory to scan for route changes. 
Camel cannot scan the classpath, so this must be configured to a file 
directory. Development with Maven as build tool, you can configure the 
directory to be src/main/resources to scan for Camel routes in XML or YAML 
files. | src/main/resources/camel | String
 | `camel.main.routesReloadDirectoryRecursive` | Whether the directory to scan 
should include sub directories. Depending on the number of sub directories, 
then this can cause the JVM to startup slower as Camel uses the JDK file-watch 
service to scan for file changes. | false | boolean
 | `camel.main.routesReloadEnabled` | Used for enabling automatic routes 
reloading. If enabled then Camel will watch for file changes in the given 
reload directory, and trigger reloading routes if files are changed. | false | 
boolean
+| `camel.main.routesReloadInterval` | How often to scan the reload directory 
for changed files, in millis. A change is reloaded at the next scan, so a 
shorter interval reloads it sooner. A longer interval groups more of one save 
together: two files saved a second apart are one change at 2000 and two at 
1000. | 2000 | long
 | `camel.main.routesReloadPattern` | Used for inclusive filtering of routes 
from directories. Typical used for specifying to accept routes in XML or YAML 
files, such as .yaml,.xml. Multiple patterns can be specified separated by 
comma. |  | String
 | `camel.main.routesReloadRemoveAllRoutes` | When reloading routes should all 
existing routes be stopped and removed. By default, Camel will stop and remove 
all existing routes before reloading routes. This ensures that only the 
reloaded routes will be active. If disabled then only routes with the same 
route id is updated, and any existing routes are continued to run. | true | 
boolean
 | `camel.main.routesReloadRestartDuration` | Whether to restart max duration 
when routes are reloaded. For example if max duration is 60 seconds, and a 
route is reloaded after 25 seconds, then this will restart the count and wait 
60 seconds again. | false | boolean
diff --git 
a/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationConfigurer.java
 
b/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationConfigurer.java
index 4e11c0784e4e..b183dc4d7471 100644
--- 
a/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationConfigurer.java
+++ 
b/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationConfigurer.java
@@ -306,6 +306,9 @@ public final class DefaultConfigurationConfigurer {
                     config.getRoutesReloadDirectory(), 
config.isRoutesReloadDirectoryRecursive());
             reloader.setPattern(config.getRoutesReloadPattern());
             
reloader.setRemoveAllRoutes(config.isRoutesReloadRemoveAllRoutes());
+            if (config.getRoutesReloadInterval() > 0) {
+                reloader.setPollTimeout(config.getRoutesReloadInterval());
+            }
             // the files that declare beans, so a bean is created again when 
its Java class changes
             reloader.setBeanResources(() -> {
                 Model model = 
camelContext.getCamelContextExtension().getContextPlugin(Model.class);
diff --git 
a/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationProperties.java
 
b/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationProperties.java
index ae5d2560af40..667b49423f1b 100644
--- 
a/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationProperties.java
+++ 
b/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationProperties.java
@@ -142,6 +142,8 @@ public abstract class DefaultConfigurationProperties<T> {
     @Metadata(defaultValue = "true")
     private boolean routesReloadRemoveAllRoutes = true;
     private boolean routesReloadRestartDuration;
+    @Metadata(defaultValue = "2000")
+    private long routesReloadInterval = 2000;
     private String groovyScriptPattern = 
"classpath:camel-groovy/*,classpath:camel-groovy-compiled/*";
     @Metadata(label = "advanced")
     private boolean groovyPreloadCompiled;
@@ -1498,6 +1500,19 @@ public abstract class DefaultConfigurationProperties<T> {
         this.routesReloadRestartDuration = routesReloadRestartDuration;
     }
 
+    public long getRoutesReloadInterval() {
+        return routesReloadInterval;
+    }
+
+    /**
+     * How often to scan the reload directory for changed files, in millis. A 
change is reloaded at the next scan, so a
+     * shorter interval reloads it sooner. A longer interval groups more of 
one save together: two files saved a second
+     * apart are one change at 2000 and two at 1000.
+     */
+    public void setRoutesReloadInterval(long routesReloadInterval) {
+        this.routesReloadInterval = routesReloadInterval;
+    }
+
     public boolean isJmxUpdateRouteEnabled() {
         return jmxUpdateRouteEnabled;
     }
@@ -2833,6 +2848,16 @@ public abstract class DefaultConfigurationProperties<T> {
         return (T) this;
     }
 
+    /**
+     * How often to scan the reload directory for changed files, in millis. A 
change is reloaded at the next scan, so a
+     * shorter interval reloads it sooner. A longer interval groups more of 
one save together: two files saved a second
+     * apart are one change at 2000 and two at 1000.
+     */
+    public T withRoutesReloadInterval(long routesReloadInterval) {
+        this.routesReloadInterval = routesReloadInterval;
+        return (T) this;
+    }
+
     /**
      * Controls whether to pool (reuse) exchanges or create new fresh 
exchanges (default). Using pooled will reduce JVM
      * garbage collection overhead by avoiding to re-create Exchange instances 
per message each consumer receives.
diff --git 
a/core/camel-main/src/test/java/org/apache/camel/main/MainRoutesReloadIntervalTest.java
 
b/core/camel-main/src/test/java/org/apache/camel/main/MainRoutesReloadIntervalTest.java
new file mode 100644
index 000000000000..23c970e2d26e
--- /dev/null
+++ 
b/core/camel-main/src/test/java/org/apache/camel/main/MainRoutesReloadIntervalTest.java
@@ -0,0 +1,74 @@
+/*
+ * 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.main;
+
+import java.nio.file.Path;
+
+import org.apache.camel.support.RouteWatcherReloadStrategy;
+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.assertNotNull;
+
+public class MainRoutesReloadIntervalTest {
+
+    @TempDir
+    Path dir;
+
+    @Test
+    public void shouldScanEveryTwoSecondsByDefault() {
+        assertEquals(2000, scanInterval(null));
+    }
+
+    @Test
+    public void shouldScanAtTheConfiguredInterval() {
+        assertEquals(500, scanInterval("500"));
+    }
+
+    @Test
+    public void shouldScanAtTheIntervalOfAnOptionalKey() {
+        // the camel_run AI tool passes the optional key, which a Camel 
without the option ignores
+        assertEquals(500, scanInterval("camel.main.?routesReloadInterval", 
"500"));
+    }
+
+    @Test
+    public void shouldKeepTheDefaultForAnIntervalOfZero() {
+        // zero would scan without a pause
+        assertEquals(2000, scanInterval("0"));
+    }
+
+    private long scanInterval(String interval) {
+        return scanInterval("camel.main.routesReloadInterval", interval);
+    }
+
+    private long scanInterval(String key, String interval) {
+        Main main = new Main();
+        
main.configure().withRoutesReloadEnabled(true).withRoutesReloadDirectory(dir.toString());
+        if (interval != null) {
+            main.addInitialProperty(key, interval);
+        }
+        main.start();
+        try {
+            RouteWatcherReloadStrategy reloader = 
main.getCamelContext().hasService(RouteWatcherReloadStrategy.class);
+            assertNotNull(reloader, "routesReloadEnabled=true should add the 
reload strategy");
+            return reloader.getPollTimeout();
+        } finally {
+            main.stop();
+        }
+    }
+}
diff --git 
a/core/camel-support/src/main/java/org/apache/camel/support/FileWatcherResourceReloadStrategy.java
 
b/core/camel-support/src/main/java/org/apache/camel/support/FileWatcherResourceReloadStrategy.java
index 4b3ae2e3f609..9e92185c506a 100644
--- 
a/core/camel-support/src/main/java/org/apache/camel/support/FileWatcherResourceReloadStrategy.java
+++ 
b/core/camel-support/src/main/java/org/apache/camel/support/FileWatcherResourceReloadStrategy.java
@@ -127,6 +127,11 @@ public class FileWatcherResourceReloadStrategy extends 
ResourceReloadStrategySup
         return folder;
     }
 
+    @ManagedAttribute(description = "How often the folder is scanned for 
changed files, in millis")
+    public long getPollTimeout() {
+        return pollTimeout;
+    }
+
     @ManagedAttribute(description = "Whether the reload strategy watches 
directory recursively")
     public boolean isRecursive() {
         return isRecursive;
diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang-mcp.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-jbang-mcp.adoc
index 3b289667a76c..a45aacc05e7f 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-mcp.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-mcp.adoc
@@ -398,8 +398,9 @@ project `directory` as an argument, the runtime tools take 
the integration `name
 | `camel_run`
 | Starts an integration from a project directory with `camel run --source-dir` 
in a separate process, in dev
   mode by default: the directory is watched, so a changed or added file (a 
route, a bean file, a Java class) is
-  reloaded. Name files to run only those, for a directory that holds several 
apps. Returns the pid, name and
-  log file once the integration is up. When an integration already runs from 
the directory, or one started
+  reloaded. It is scanned every 500 ms (`camel.main.routesReloadInterval`), 
not every 2 seconds as by
+  `camel run --dev`, so a write is reloaded and answered sooner. Name files to 
run only those, for a directory
+  that holds several apps. Returns the pid, name and log file once the 
integration is up. When an integration already runs from the directory, or one 
started
   earlier is still starting, no second copy is started: the answer is that one 
(`status: running` with its
   pid, name and log file, or `status: starting`), with what dev mode and 
`camel_control restart` do for it.
   When the start fails, the answer has the cause from the console output: each 
log record with its exception
diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang-running.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-jbang-running.adoc
index 9505dc9628c7..bcf3613c7fef 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-running.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-running.adoc
@@ -17,6 +17,17 @@ camel run foo.yaml --dev
 
 This works for all DSLs (YAML, Java, XML).
 
+Dev mode looks for changed files every 2 seconds. To look more often, set 
`camel.main.routesReloadInterval`
+(in millis):
+
+[source,bash]
+----
+camel run foo.yaml --dev --prop=camel.main.routesReloadInterval=500
+----
+
+A shorter interval reloads a change sooner. A longer one groups more of one 
save together, such as several files
+an editor saves one after the other.
+
 `camel dev` is a shortcut for `camel run --dev`, and without files it runs the 
current folder
 (as `--source-dir=.`):
 
diff --git a/docs/user-manual/modules/ROOT/pages/route-reload.adoc 
b/docs/user-manual/modules/ROOT/pages/route-reload.adoc
index ad80593a7b25..3ffcd96d5350 100644
--- a/docs/user-manual/modules/ROOT/pages/route-reload.adoc
+++ b/docs/user-manual/modules/ROOT/pages/route-reload.adoc
@@ -57,6 +57,7 @@ You can configure the `RouteWatcherReloadStrategy` using the 
following options:
 | `routesReloadDirectory` | Directory to scan for route changes. Camel cannot 
scan the classpath, so this must be configured to a file directory. Development 
with Maven as build tool, you can configure the directory to be 
`src/main/resources` to scan for Camel routes in XML or YAML files. | 
`src/main/resources/camel` | String
 | `routesReloadDirectoryRecursive` | Whether the directory to scan should 
include subdirectories. Depending on the number of sub-directories, then this 
can cause the JVM to start up slower as Camel uses the JDK file-watch service 
to scan for file changes. | `false` | boolean
 | `routesReloadEnabled` | Used for enabling automatic routes reloading. If 
enabled, then Camel will watch for file changes in the given reload directory, 
and trigger reloading routes if files are changed. | `false` | boolean
+| `routesReloadInterval` | How often to scan the reload directory for changed 
files, in millis. A change is reloaded at the next scan, so a shorter interval 
reloads it sooner. A longer interval groups more of one save together: two 
files saved a second apart are one change at 2000 and two at 1000. | `2000` | 
long
 | `routesReloadPattern` | Used for inclusive filtering of routes from 
directories. Typical used for specifying to accept routes in XML or YAML files. 
The default pattern is \*.yaml,*.xml Multiple patterns can be specified 
separated by comma. | `\*.yaml,*.xml`  | String
 | `routesReloadRemoveAllRoutes` | When reloading routes should all existing 
routes be stopped and removed. By default, Camel will stop and remove all 
existing routes before reloading routes. This ensures that only the reloaded 
routes will be active. If disabled, then only routes with the same route id are 
updated, and any existing routes are continued to run. | `true` | boolean
 |=======================================================================
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/IntegrationLauncher.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/IntegrationLauncher.java
index 75d8046ccd8f..8577ede67471 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/IntegrationLauncher.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/IntegrationLauncher.java
@@ -49,6 +49,16 @@ public final class IntegrationLauncher {
     /** The last log records of a failed start that are returned. */
     static final int FAILURE_RECORDS = 15;
 
+    /**
+     * How often an integration started in dev mode scans for changed files 
(CAMEL-25661). A write tool waits for the
+     * reload of the file it wrote, and dev mode finds the change at its next 
scan: at the default of 2000 ms that is
+     * about a second per write. Each write tool call writes one file and 
waits for its reload, so a shorter interval
+     * does not split one change into two reloads. The key is optional (the ? 
before the name): the camel command can be
+     * older than these tools (the MCP server run as LATEST, an older camel on 
the PATH), and a Camel without the option
+     * logs a warning and starts, where an unknown key would stop it from 
starting.
+     */
+    static final String DEV_RELOAD_INTERVAL = 
"--prop=camel.main.?routesReloadInterval=500";
+
     /** A stack frame, or the count of the frames a cause shares with the 
exception it caused. */
     private static final Pattern FRAME = Pattern.compile("^\\s*(at 
\\S.*|\\.\\.\\. \\d+ (more|common frames omitted))$");
 
@@ -214,6 +224,7 @@ public final class IntegrationLauncher {
         cmd.add("--source-dir=.");
         if (dev) {
             cmd.add("--dev");
+            cmd.add(DEV_RELOAD_INTERVAL);
         }
         if (name != null && !name.isBlank()) {
             cmd.add("--name=" + name);
@@ -240,6 +251,7 @@ public final class IntegrationLauncher {
         cmd.addAll(files);
         if (dev) {
             cmd.add("--dev");
+            cmd.add(DEV_RELOAD_INTERVAL);
         }
         if (name != null && !name.isBlank()) {
             cmd.add("--name=" + name);
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ReloadOutcome.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ReloadOutcome.java
index d3759fcc391b..f816c0b87369 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ReloadOutcome.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ReloadOutcome.java
@@ -20,6 +20,7 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.Locale;
+import java.util.function.Supplier;
 import java.util.regex.Pattern;
 
 import org.apache.camel.util.StopWatch;
@@ -38,6 +39,18 @@ public final class ReloadOutcome {
     static final String PROPERTIES = "Reloading properties";
     static final String REPORT = "did not load";
 
+    /**
+     * How often the log is read while waiting: a reload or a line the routes 
logged is seen at most this long after it
+     * was logged, and the write answers that much sooner (CAMEL-25661). Only 
the tail of the log is read.
+     */
+    static final long POLL_MILLIS = 100;
+
+    /**
+     * How long a properties reload is watched for the reload of the routes 
after it: the routes are reloaded after the
+     * properties they use, and a route that fails with the new values is the 
outcome the agent must hear.
+     */
+    static final long ROUTES_AFTER_PROPERTIES_MILLIS = 500;
+
     private ReloadOutcome() {
     }
 
@@ -120,19 +133,38 @@ public final class ReloadOutcome {
 
     /** Polls the integration's log for the reload of a just written file, up 
to the timeout. */
     public static JsonObject await(long pid, String name, String sinceKey, 
long timeoutMillis) {
+        return await(() -> records(pid, name), sinceKey, timeoutMillis);
+    }
+
+    /**
+     * Polls the records for the reload of a just written file, up to the 
timeout. A properties reload is watched
+     * {@link #ROUTES_AFTER_PROPERTIES_MILLIS} longer for the routes reload 
after it, whose outcome is then the answer.
+     */
+    static JsonObject await(Supplier<List<JsonObject>> records, String 
sinceKey, long timeoutMillis) {
         StopWatch watch = new StopWatch();
+        JsonObject properties = null;
+        long propertiesAt = 0;
         while (watch.taken() < timeoutMillis) {
-            JsonObject outcome = classify(records(pid, name), sinceKey);
-            if (outcome != null) {
+            JsonObject outcome = classify(records.get(), sinceKey);
+            if (outcome != null && 
!"properties".equals(outcome.getString("status"))) {
                 return outcome;
             }
+            if (outcome != null && properties == null) {
+                properties = outcome;
+                propertiesAt = watch.taken();
+            } else if (properties != null && watch.taken() - propertiesAt >= 
ROUTES_AFTER_PROPERTIES_MILLIS) {
+                return properties;
+            }
             try {
-                Thread.sleep(500);
+                Thread.sleep(POLL_MILLIS);
             } catch (InterruptedException e) {
                 Thread.currentThread().interrupt();
                 break;
             }
         }
+        if (properties != null) {
+            return properties;
+        }
         JsonObject out = new JsonObject();
         out.put("status", "unknown");
         out.put("message", "no reload seen in the log within " + timeoutMillis 
/ 1000 + "s: the integration may not run"
@@ -159,7 +191,7 @@ public final class ReloadOutcome {
         JsonArray out = output(records(pid, name));
         while (out.isEmpty() && watch.taken() < timeoutMillis) {
             try {
-                Thread.sleep(300);
+                Thread.sleep(POLL_MILLIS);
             } catch (InterruptedException e) {
                 Thread.currentThread().interrupt();
                 break;
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/IntegrationLauncherTest.java
 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/IntegrationLauncherTest.java
index daf494767ceb..5e1eda1d6eef 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/IntegrationLauncherTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/IntegrationLauncherTest.java
@@ -42,7 +42,7 @@ class IntegrationLauncherTest {
                 .containsExactly("a.camel.yaml", "application.properties", 
"b.camel.yaml");
         
assertThat(IntegrationLauncher.runArguments(IntegrationLauncher.sourceFiles(dir),
 null, true, null))
                 .containsExactly("run", "a.camel.yaml", 
"application.properties", "b.camel.yaml", "--dev",
-                        "--logging-color=false");
+                        "--prop=camel.main.?routesReloadInterval=500", 
"--logging-color=false");
         
assertThat(IntegrationLauncher.sourceFiles(dir.resolve("nope"))).isEmpty();
     }
 
@@ -50,7 +50,8 @@ class IntegrationLauncherTest {
     @Test
     void noFilesRunsTheDirectoryAsTheApp() {
         assertThat(IntegrationLauncher.sourceDirArguments(null, true, null))
-                .containsExactly("run", "--source-dir=.", "--dev", 
"--logging-color=false");
+                .containsExactly("run", "--source-dir=.", "--dev", 
"--prop=camel.main.?routesReloadInterval=500",
+                        "--logging-color=false");
         assertThat(IntegrationLauncher.sourceDirArguments("demo", false, 
List.of("--port=9000")))
                 .containsExactly("run", "--source-dir=.", "--name=demo", 
"--logging-color=false", "--port=9000");
     }
@@ -59,8 +60,9 @@ class IntegrationLauncherTest {
     void filesNameAndExtraArgumentsArePassedThrough() {
         assertThat(IntegrationLauncher.runArguments(List.of("a.camel.yaml", 
"application.properties"), "demo", true,
                 List.of("--port=9000")))
-                .containsExactly("run", "a.camel.yaml", 
"application.properties", "--dev", "--name=demo",
-                        "--logging-color=false", "--port=9000");
+                .containsExactly("run", "a.camel.yaml", 
"application.properties", "--dev",
+                        "--prop=camel.main.?routesReloadInterval=500", 
"--name=demo", "--logging-color=false",
+                        "--port=9000");
     }
 
     /**
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/ReloadOutcomeTest.java
 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/ReloadOutcomeTest.java
index 11343b1776c1..82b97f3e4904 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/ReloadOutcomeTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/ReloadOutcomeTest.java
@@ -17,6 +17,7 @@
 package org.apache.camel.dsl.jbang.core.commands.ai;
 
 import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
 
 import org.apache.camel.util.json.JsonArray;
 import org.apache.camel.util.json.JsonObject;
@@ -44,6 +45,9 @@ class ReloadOutcomeTest {
             "Caused by: java.lang.IllegalArgumentException: Option name is 
required when creating endpoint uri with syntax cron:name",
             "\tat 
org.apache.camel.support.component.AbstractApiEndpoint.x(Foo.java:1)");
 
+    private static final List<String> PROPERTIES = List.of(
+            "2026-09-21 10:00:30.000  INFO 42 --- [rReloadStrategy] 
org.apache.camel.main.DefaultConfigurationConfigurer : Reloading properties: 
file:application.properties");
+
     private static final List<String> RELOADED = List.of(
             "2026-09-21 10:00:40.000  INFO 42 --- [rReloadStrategy] 
org.apache.camel.support.RouteWatcherReloadStrategy : Routes reloaded summary 
(total:1 started:1)",
             "2026-09-21 10:00:41.000  INFO 42 --- [ timer://tick] route1 : 
Hello again");
@@ -80,14 +84,32 @@ class ReloadOutcomeTest {
 
     @Test
     void aPropertiesReloadIsReportedAsSuch() {
-        List<String> props = List.of(
-                "2026-09-21 10:00:30.000  INFO 42 --- [rReloadStrategy] 
org.apache.camel.main.DefaultConfigurationConfigurer : Reloading properties: 
file:application.properties");
         String since = ReloadOutcome.latestReloadKey(records(BEFORE));
-        JsonObject out = ReloadOutcome.classify(records(BEFORE, props), since);
+        JsonObject out = ReloadOutcome.classify(records(BEFORE, PROPERTIES), 
since);
         assertEquals("properties", out.getString("status"));
         assertEquals("Reloading properties: file:application.properties", 
out.getString("message"));
     }
 
+    /** CAMEL-25661: the routes are reloaded after the properties they use, 
and their outcome is the answer. */
+    @Test
+    void aPropertiesReloadIsAnsweredWithTheRoutesReloadAfterIt() {
+        String since = ReloadOutcome.latestReloadKey(records(BEFORE));
+        List<List<JsonObject>> polls
+                = List.of(records(BEFORE), records(BEFORE, PROPERTIES), 
records(BEFORE, PROPERTIES, RELOADED));
+        AtomicInteger poll = new AtomicInteger();
+        JsonObject out = ReloadOutcome.await(() -> 
polls.get(Math.min(poll.getAndIncrement(), polls.size() - 1)), since,
+                5000);
+        assertEquals("reloaded", out.getString("status"));
+        assertEquals("Routes reloaded summary (total:1 started:1)", 
out.getString("message"));
+    }
+
+    @Test
+    void aPropertiesReloadWithoutARoutesReloadAfterItIsReportedAsSuch() {
+        String since = ReloadOutcome.latestReloadKey(records(BEFORE));
+        JsonObject out = ReloadOutcome.await(() -> records(BEFORE, 
PROPERTIES), since, 5000);
+        assertEquals("properties", out.getString("status"));
+    }
+
     @Test
     void aReloadAfterTheFailureIsReloaded() {
         String since = ReloadOutcome.latestReloadKey(records(BEFORE));

Reply via email to