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 52e3a5fb73d9 CAMEL-25767: camel-main - a properties file that changed
in dev mode applies its beans and components (#28123)
52e3a5fb73d9 is described below
commit 52e3a5fb73d929c8dfa54328caff1f12404dfcb4
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Oct 19 20:27:39 2026 +0200
CAMEL-25767: camel-main - a properties file that changed in dev mode
applies its beans and components (#28123)
Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
---
.../org/apache/camel/spi/PropertiesReload.java | 16 +++
.../RouteWatcherReloadPropertiesLocationTest.java | 111 +++++++++++++++
.../org/apache/camel/main/BaseMainSupport.java | 58 ++++++++
.../apache/camel/main/MainPropertiesReload.java | 12 +-
.../camel/main/MainPropertiesReloadBeansTest.java | 158 +++++++++++++++++++++
.../camel/support/RouteWatcherReloadStrategy.java | 49 ++++++-
.../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc | 19 +++
7 files changed, 418 insertions(+), 5 deletions(-)
diff --git
a/core/camel-api/src/main/java/org/apache/camel/spi/PropertiesReload.java
b/core/camel-api/src/main/java/org/apache/camel/spi/PropertiesReload.java
index 457319c014ad..88fdef1f0143 100644
--- a/core/camel-api/src/main/java/org/apache/camel/spi/PropertiesReload.java
+++ b/core/camel-api/src/main/java/org/apache/camel/spi/PropertiesReload.java
@@ -44,4 +44,20 @@ public interface PropertiesReload {
*/
void onReload(String name, Properties properties) throws Exception;
+ /**
+ * Callback when a resource with properties changed and is re-loaded, such
as a properties file that was saved while
+ * live reloading is enabled. Unlike {@link #onReload(String,
Properties)}, the properties are only those that
+ * changed, as the user edited them, so a listener can tell which changes
it cannot apply without a restart.
+ * <p/>
+ * The default calls {@link #onReload(String, Properties)}.
+ *
+ * @param name name of the resource such as the file name (absolute)
+ * @param properties the properties that changed
+ * @throws Exception error reloading properties
+ * @since 4.24
+ */
+ default void onChanged(String name, Properties properties) throws
Exception {
+ onReload(name, properties);
+ }
+
}
diff --git
a/core/camel-core/src/test/java/org/apache/camel/support/RouteWatcherReloadPropertiesLocationTest.java
b/core/camel-core/src/test/java/org/apache/camel/support/RouteWatcherReloadPropertiesLocationTest.java
new file mode 100644
index 000000000000..11bf60448572
--- /dev/null
+++
b/core/camel-core/src/test/java/org/apache/camel/support/RouteWatcherReloadPropertiesLocationTest.java
@@ -0,0 +1,111 @@
+/*
+ * 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.ArrayList;
+import java.util.List;
+import java.util.Properties;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.spi.PropertiesReload;
+import org.apache.camel.support.service.ServiceSupport;
+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-25767: a properties file that changed in dev mode is configured from.
camel run --source-dir=. knows
+ * application.properties as file:./application.properties, and the file
watcher reports file:application.properties:
+ * the two were compared as text, so the configuration in the file was never
applied (only its placeholders, as the file
+ * was added once more).
+ */
+public class RouteWatcherReloadPropertiesLocationTest extends
ContextTestSupport {
+
+ @TempDir
+ Path dir;
+
+ private final List<String> changes = new ArrayList<>();
+ private String known;
+ private Path file;
+
+ @Override
+ protected CamelContext createCamelContext() throws Exception {
+ CamelContext context = super.createCamelContext();
+ file = Files.writeString(dir.resolve("app.properties"), "greeting =
hello\n");
+ known = "file:" + dir + "/./app.properties";
+ context.getPropertiesComponent().setLocation(known);
+ context.addService(new RecordingPropertiesReload(changes));
+ return context;
+ }
+
+ @Test
+ public void theFileThatChangedIsTheFileThePropertiesComponentKnows()
throws Exception {
+ Files.writeString(file, "greeting =
hello\ncamel.component.log.exchange-formatter = #bean:myFormatter\n");
+
+ boolean reloaded =
strategy().onPropertiesReload(ResourceHelper.resolveResource(context, "file:" +
file), false);
+
+ assertTrue(reloaded);
+ assertEquals(List.of("changed " + known + ":
camel.component.log.exchange-formatter"), changes);
+ // the file is not added a second time
+ assertEquals(1,
context.getPropertiesComponent().getLocations().size());
+ }
+
+ @Test
+ public void theConfigurationOfANewPropertiesFileIsAppliedToo() throws
Exception {
+ Path other = Files.writeString(dir.resolve("other.properties"),
+ "camel.component.log.exchange-formatter =
#bean:myFormatter\n");
+
+ boolean reloaded =
strategy().onPropertiesReload(ResourceHelper.resolveResource(context, "file:" +
other), false);
+
+ assertTrue(reloaded);
+ assertEquals(List.of("changed file:" + other + ":
camel.component.log.exchange-formatter"), changes);
+ // and its properties resolve
+ assertEquals("#bean:myFormatter",
+
context.getPropertiesComponent().resolveProperty("camel.component.log.exchange-formatter").orElse(null));
+ }
+
+ private RouteWatcherReloadStrategy strategy() {
+ RouteWatcherReloadStrategy strategy = new RouteWatcherReloadStrategy();
+ strategy.setCamelContext(context);
+ return strategy;
+ }
+
+ // records what the reload hands to the listener, which camel-main
configures from
+ private static class RecordingPropertiesReload extends ServiceSupport
implements PropertiesReload {
+
+ private final List<String> changes;
+
+ RecordingPropertiesReload(List<String> changes) {
+ this.changes = changes;
+ }
+
+ @Override
+ public void onReload(String name, Properties properties) {
+ changes.add("reloaded " + name + ": " + String.join(", ",
properties.stringPropertyNames()));
+ }
+
+ @Override
+ public void onChanged(String name, Properties properties) {
+ changes.add("changed " + name + ": " + String.join(", ",
properties.stringPropertyNames()));
+ }
+ }
+}
diff --git
a/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java
b/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java
index 3199ff2b0acd..87f87a8d12cb 100644
--- a/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java
+++ b/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java
@@ -2797,6 +2797,47 @@ public abstract class BaseMainSupport extends
BaseService {
return bean;
}
+ /**
+ * The beans of re-loaded properties (CAMEL-25767). A new bean is created
and bound as when the application starts,
+ * so a component configured in the same reload can refer to it, and a
component created later can autowire it. A
+ * bean that exists already is left as it is, as components and routes may
hold it: when its properties changed in a
+ * properties file, it is logged that this needs a restart.
+ */
+ private void bindReloadedBeans(
+ CamelContext camelContext, OrderedLocationProperties
reloadedProperties, boolean changed,
+ OrderedLocationProperties autoConfiguredProperties)
+ throws Exception {
+
+ OrderedLocationProperties created = new OrderedLocationProperties();
+ Set<String> kept = new LinkedHashSet<>();
+ // stringPropertyNames is a live view of the keys, which are removed
below
+ for (String key : new
ArrayList<>(reloadedProperties.stringPropertyNames())) {
+ if (!startsWithIgnoreCase(key, "camel.beans.")) {
+ continue;
+ }
+ // the name the bean is bound with, as when the application starts
+ String option = optionKey(key.substring(12));
+ String name = StringHelper.before(option, ".", option);
+ name = StringHelper.before(name, "[", name);
+ if (camelContext.getRegistry().lookupByName(name) == null) {
+ created.put(reloadedProperties.getLocation(key), option,
reloadedProperties.getProperty(key));
+ } else {
+ kept.add(name);
+ }
+ reloadedProperties.remove(key);
+ }
+ if (!created.isEmpty()) {
+ LOG.debug("Creating and binding beans to registry from reloaded
properties: {}", created.size());
+ bindBeansToRegistry(camelContext, created, "camel.beans.",
+ mainConfigurationProperties.isAutoConfigurationFailFast(),
+
mainConfigurationProperties.isAutoConfigurationLogSummary(), true,
autoConfiguredProperties);
+ }
+ if (changed && !kept.isEmpty()) {
+ LOG.warn("The properties of the bean(s) {} changed. A reload only
creates new beans: restart to apply the change.",
+ String.join(", ", kept));
+ }
+ }
+
private void bindBeansToRegistry(
CamelContext camelContext, OrderedLocationProperties properties,
String optionPrefix, boolean failIfNotSet, boolean logSummary,
boolean ignoreCase,
@@ -3071,6 +3112,21 @@ public abstract class BaseMainSupport extends
BaseService {
protected void autoConfigurationFromReloadedProperties(
CamelContext camelContext, OrderedLocationProperties
reloadedProperties)
throws Exception {
+ autoConfigurationFromReloadedProperties(camelContext,
reloadedProperties, false);
+ }
+
+ /**
+ * Configures from properties that were re-loaded.
+ *
+ * @param camelContext the context
+ * @param reloadedProperties the properties
+ * @param changed whether these are the properties of a
properties file that changed, such as
+ * application.properties in dev mode, rather
than options that are applied again (such as
+ * by a context reload, for secret rotation)
+ */
+ protected void autoConfigurationFromReloadedProperties(
+ CamelContext camelContext, OrderedLocationProperties
reloadedProperties, boolean changed)
+ throws Exception {
Map<PropertyOptionKey, OrderedLocationProperties> properties = new
LinkedHashMap<>();
@@ -3087,6 +3143,8 @@ public abstract class BaseMainSupport extends BaseService
{
}
OrderedLocationProperties autoConfiguredProperties = new
OrderedLocationProperties();
+ // the beans first, as the components that are configured next may
refer to them (CAMEL-25767)
+ bindReloadedBeans(camelContext, reloadedProperties, changed,
autoConfiguredProperties);
doAutoConfigurationFromProperties(camelContext, reloadedProperties,
properties, true, autoConfiguredProperties);
// log summary of configurations
diff --git
a/core/camel-main/src/main/java/org/apache/camel/main/MainPropertiesReload.java
b/core/camel-main/src/main/java/org/apache/camel/main/MainPropertiesReload.java
index c02ba5a8dcad..7abdd23bed33 100644
---
a/core/camel-main/src/main/java/org/apache/camel/main/MainPropertiesReload.java
+++
b/core/camel-main/src/main/java/org/apache/camel/main/MainPropertiesReload.java
@@ -51,8 +51,18 @@ public class MainPropertiesReload extends ServiceSupport
@Override
public void onReload(String name, Properties properties) throws Exception {
+ // such as a context reload, which applies the options with a property
placeholder again (secret rotation)
if (properties instanceof OrderedLocationProperties prop) {
- main.autoConfigurationFromReloadedProperties(camelContext, prop);
+ main.autoConfigurationFromReloadedProperties(camelContext, prop,
false);
+ main.autowireWildcardProperties(camelContext);
+ }
+ }
+
+ @Override
+ public void onChanged(String name, Properties properties) throws Exception
{
+ // a properties file that changed, such as application.properties in
dev mode: the properties that changed
+ if (properties instanceof OrderedLocationProperties prop) {
+ main.autoConfigurationFromReloadedProperties(camelContext, prop,
true);
main.autowireWildcardProperties(camelContext);
}
}
diff --git
a/core/camel-main/src/test/java/org/apache/camel/main/MainPropertiesReloadBeansTest.java
b/core/camel-main/src/test/java/org/apache/camel/main/MainPropertiesReloadBeansTest.java
new file mode 100644
index 000000000000..13008ed0c98a
--- /dev/null
+++
b/core/camel-main/src/test/java/org/apache/camel/main/MainPropertiesReloadBeansTest.java
@@ -0,0 +1,158 @@
+/*
+ * 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.util.List;
+import java.util.Properties;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.component.log.LogComponent;
+import org.apache.camel.support.processor.DefaultExchangeFormatter;
+import org.apache.camel.util.OrderedLocationProperties;
+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 static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * CAMEL-25767: a properties file that changed creates the new beans in it, so
a component that is configured in the
+ * same reload can refer to them. A bean that exists already is left as it is,
as components may hold it, and a change
+ * to it in a properties file is logged as needing a restart.
+ */
+public class MainPropertiesReloadBeansTest {
+
+ private static final String FORMATTER =
"#class:org.apache.camel.support.processor.DefaultExchangeFormatter";
+
+ private final List<String> warnings = new CopyOnWriteArrayList<>();
+ private Appender appender;
+
+ @BeforeEach
+ public void captureWarnings() {
+ appender = new AbstractAppender("reload-warnings", null, null, true,
Property.EMPTY_ARRAY) {
+ @Override
+ public void append(LogEvent event) {
+ if (event.getLevel().isMoreSpecificThan(Level.WARN)) {
+ warnings.add(event.getMessage().getFormattedMessage());
+ }
+ }
+ };
+ appender.start();
+ LoggerContext context = (LoggerContext) LogManager.getContext(false);
+ context.getConfiguration().getRootLogger().addAppender(appender,
Level.ALL, null);
+ context.updateLoggers();
+ }
+
+ @AfterEach
+ public void stopCapture() {
+ LoggerContext context = (LoggerContext) LogManager.getContext(false);
+
context.getConfiguration().getRootLogger().removeAppender(appender.getName());
+ context.updateLoggers();
+ appender.stop();
+ }
+
+ @Test
+ public void aNewBeanIsCreatedAndAComponentOfTheSameReloadRefersToIt()
throws Exception {
+ Main main = main(new Properties());
+ try {
+ main.start();
+ CamelContext context = main.getCamelContext();
+
+ // application.properties saved in dev mode: a new bean, and the
log component configured with it
+ reload(main).onChanged("file:application.properties", properties(
+ "camel.beans.myFormatter", FORMATTER,
+ "camel.beans.myFormatter.show-all", "true",
+ "camel.component.log.exchange-formatter",
"#bean:myFormatter"));
+
+ DefaultExchangeFormatter bean
+ = context.getRegistry().lookupByNameAndType("myFormatter",
DefaultExchangeFormatter.class);
+ assertThat(bean).isNotNull();
+ assertThat(bean.isShowAll()).isTrue();
+ assertThat(context.getComponent("log",
LogComponent.class).getExchangeFormatter()).isSameAs(bean);
+ assertThat(warnings).noneMatch(w -> w.contains("restart"));
+ } finally {
+ main.stop();
+ }
+ }
+
+ @Test
+ public void aBeanThatExistsIsLeftAsItIsAndNeedsARestart() throws Exception
{
+ Main main = main(properties("camel.beans.myFormatter", FORMATTER));
+ try {
+ main.start();
+ CamelContext context = main.getCamelContext();
+ DefaultExchangeFormatter bean
+ = context.getRegistry().lookupByNameAndType("myFormatter",
DefaultExchangeFormatter.class);
+
+ reload(main).onChanged("file:application.properties",
properties("camel.beans.myFormatter.show-all", "true"));
+
+
assertThat(context.getRegistry().lookupByName("myFormatter")).isSameAs(bean);
+ assertThat(bean.isShowAll()).isFalse();
+ assertThat(warnings).anyMatch(w -> w.contains("myFormatter") &&
w.contains("restart"));
+ } finally {
+ main.stop();
+ }
+ }
+
+ @Test
+ public void aContextReloadLeavesABeanThatExistsWithoutAWarning() throws
Exception {
+ Main main = main(properties("camel.beans.myFormatter", FORMATTER));
+ try {
+ main.start();
+ CamelContext context = main.getCamelContext();
+ DefaultExchangeFormatter bean
+ = context.getRegistry().lookupByNameAndType("myFormatter",
DefaultExchangeFormatter.class);
+
+ // a context reload applies the options with a property
placeholder again (secret rotation)
+ reload(main).onReload("ContextReload",
properties("camel.beans.myFormatter.show-all", "true"));
+
+
assertThat(context.getRegistry().lookupByName("myFormatter")).isSameAs(bean);
+ assertThat(warnings).noneMatch(w -> w.contains("restart"));
+ } finally {
+ main.stop();
+ }
+ }
+
+ private static Main main(Properties properties) {
+ Main main = new Main();
+ main.setDefaultPropertyPlaceholderLocation("false");
+ main.setOverrideProperties(properties);
+ return main;
+ }
+
+ private static MainPropertiesReload reload(Main main) {
+ MainPropertiesReload reload = new MainPropertiesReload(main);
+ reload.setCamelContext(main.getCamelContext());
+ return reload;
+ }
+
+ private static OrderedLocationProperties properties(String...
keysAndValues) {
+ OrderedLocationProperties answer = new OrderedLocationProperties();
+ for (int i = 0; i < keysAndValues.length; i += 2) {
+ answer.put("file:application.properties", keysAndValues[i],
keysAndValues[i + 1]);
+ }
+ return answer;
+ }
+}
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 bb9d767da998..5c91fff49ae1 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
@@ -20,6 +20,7 @@ import java.io.File;
import java.io.InputStream;
import java.net.URI;
import java.nio.file.Files;
+import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayDeque;
@@ -357,6 +358,10 @@ public class RouteWatcherReloadStrategy extends
FileWatcherResourceReloadStrateg
}
protected boolean onPropertiesReload(Resource resource, boolean
reloadRoutes) throws Exception {
+ // the location the properties component knows the file by, which can
be written another way than the file
+ // that changed, such as file:./application.properties for
file:application.properties (CAMEL-25767)
+ String location = knownLocation(resource);
+
// optimize to only update if something changed
OrderedLocationProperties changed = null;
@@ -372,7 +377,7 @@ public class RouteWatcherReloadStrategy extends
FileWatcherResourceReloadStrateg
IOHelper.close(is);
}
changed = new OrderedLocationProperties();
- changed.putAll(resource.getLocation(), tmp);
+ changed.putAll(location, tmp);
// filter to only keep changed properties
pc.keepOnlyChangeProperties(changed);
}
@@ -381,16 +386,16 @@ public class RouteWatcherReloadStrategy extends
FileWatcherResourceReloadStrateg
if (changed != null && !changed.isEmpty()) {
LOG.info("Reloading properties: {}. (Only Camel routes and
components can be updated with changes)",
resource.getLocation());
- reloaded = pc.reloadProperties(resource.getLocation());
+ reloaded = pc.reloadProperties(location);
if (reloaded) {
- pr.onReload(resource.getLocation(), changed);
+ pr.onChanged(location, changed);
// trigger all routes to be reloaded
if (reloadRoutes) {
retryFailedOrReloadAll();
}
} else {
// this may be a new properties file, so we need to add as new
known location
- String existing =
getPropertiesByLocation(resource.getLocation());
+ String existing = getPropertiesByLocation(location);
if (existing == null) {
// remove scheme
String loc = resource.getLocation();
@@ -400,6 +405,11 @@ public class RouteWatcherReloadStrategy extends
FileWatcherResourceReloadStrateg
PropertiesSource ps =
pc.getPropertiesSourceFactory().newFilePropertiesSource(loc);
pc.addPropertiesSource(ps);
reloaded = true;
+ // the configuration in a new properties file is applied
as well (CAMEL-25767)
+ pr.onChanged(location, changed);
+ if (reloadRoutes) {
+ retryFailedOrReloadAll();
+ }
}
}
}
@@ -427,6 +437,37 @@ public class RouteWatcherReloadStrategy extends
FileWatcherResourceReloadStrateg
}
}
+ /**
+ * The location of the properties component for the file of the resource,
as the same file can be written in other
+ * ways (file:./application.properties and file:application.properties),
else the location of the resource.
+ */
+ private String knownLocation(Resource resource) {
+ String location = resource.getLocation();
+ Path file = filePath(location);
+ if (file == null) {
+ return location;
+ }
+ for (String s :
getCamelContext().getPropertiesComponent().getLocations()) {
+ String known = s.endsWith(";optional=true") ? s.substring(0,
s.length() - 14) : s;
+ if (file.equals(filePath(known))) {
+ return known;
+ }
+ }
+ return location;
+ }
+
+ // the absolute path of a file: location, or null
+ private static Path filePath(String location) {
+ if (location == null || !location.startsWith("file:")) {
+ return null;
+ }
+ try {
+ return
Paths.get(location.substring(5)).toAbsolutePath().normalize();
+ } catch (InvalidPathException e) {
+ return null;
+ }
+ }
+
private String getPropertiesByLocation(String loc) {
PropertiesComponent pc = getCamelContext().getPropertiesComponent();
for (String s : pc.getLocations()) {
diff --git
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
index be17cc8fe5c7..f73972ebe986 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
@@ -1808,3 +1808,22 @@ the one of a miss, with the lines the file has there.
`matchedLines` in the answ
`~/.camel/camel-launch-<number>.log` or `camel-restart-<number>.log`, which
stayed until the MCP server exited. An
integration that still runs when the server exits keeps the file while it
runs: `camel stop` removes it, and
`camel dirty --clean` once the integration has ended.
+
+=== camel-main - a properties file that changed while reloading applies its
beans and components
+
+With live reloading (such as `camel run --dev`), a change to
application.properties now applies its configuration:
+
+* The file is matched to the properties file the application loaded, also when
its location is written another way:
+ `camel run --source-dir=.` loads `file:./application.properties`, and the
file watcher reports
+ `file:application.properties`. Before, the two did not match, so the file
was added once more for its property
+ placeholders, but its `camel.component.*`, `camel.dataformat.*` and
`camel.language.*` options were not applied.
+* A new bean (`camel.beans.*`) is created and bound, before the components are
configured again, so a component can
+ refer to it (`#bean:name`), and a component that is created later can
autowire it. With camel-jbang, the class of
+ the bean is downloaded when camel-jbang knows it, such as the Artemis
client. A dependency that is added to
+ `camel.jbang.dependencies` while the application runs is not downloaded
until a restart (CAMEL-25833).
+* A bean that exists is left as it is, as components may hold it. When its
properties change, a `WARN` says that a
+ restart is needed.
+* A properties file that is created while the application runs is applied as
well.
+
+`PropertiesReload` has a new default method, `onChanged`, which a properties
file that changed calls with the properties
+that changed. A context reload (such as for secret rotation) still calls
`onReload`.