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 383f303bd89c CAMEL-25661: camel-jbang - AI write tools reload sooner:
camel_run scans every 500 ms (#27899)
383f303bd89c is described below
commit 383f303bd89c6627fb47f3a5a1dc039466d0c328
Author: Claus Ibsen <[email protected]>
AuthorDate: Thu Oct 15 15:05:35 2026 +0200
CAMEL-25661: camel-jbang - AI write tools reload sooner: camel_run scans
every 500 ms (#27899)
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.
Claude-Session: https://claude.ai/code/session_01STT6whBgK1AqsSsUKrnE8m
Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
---
.../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 40fb5d7743a8..0d7994b61f72 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));