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 45b7d77b15c7 CAMEL-25068: camel-core - Route templates: the caller's 
bean wins over a template bean of the same name (item 1) (#27466)
45b7d77b15c7 is described below

commit 45b7d77b15c7bb21e20192efda1807497404d6bc
Author: allthingssecurity <[email protected]>
AuthorDate: Wed Oct 7 15:56:48 2026 +0530

    CAMEL-25068: camel-core - Route templates: the caller's bean wins over a 
template bean of the same name (item 1) (#27466)
    
    When the caller binds a bean with the same name as a templateBean, which 
bean the route got depended on how it was bound and looked up. The caller's 
bean now consistently wins over a template bean of the same name (CAMEL-25068 
item 1).
    
    Closes #27466
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
 .../java/org/apache/camel/impl/DefaultModel.java   |  12 +++
 .../camel/builder/TemplatedRouteBuilder.java       |   9 +-
 .../camel/builder/RouteTemplateCallerBeanTest.java | 104 +++++++++++++++++++++
 .../ROOT/pages/camel-4x-upgrade-guide-4_23.adoc    |   6 ++
 .../modules/ROOT/pages/route-template.adoc         |   6 ++
 5 files changed, 134 insertions(+), 3 deletions(-)

diff --git 
a/core/camel-core-engine/src/main/java/org/apache/camel/impl/DefaultModel.java 
b/core/camel-core-engine/src/main/java/org/apache/camel/impl/DefaultModel.java
index 0bf095fdb152..0d740ce89a6c 100644
--- 
a/core/camel-core-engine/src/main/java/org/apache/camel/impl/DefaultModel.java
+++ 
b/core/camel-core-engine/src/main/java/org/apache/camel/impl/DefaultModel.java
@@ -20,8 +20,10 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.StringJoiner;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.locks.Lock;
@@ -64,6 +66,7 @@ import org.apache.camel.spi.NodeIdFactory;
 import org.apache.camel.spi.RouteTemplateLoaderListener;
 import org.apache.camel.spi.RouteTemplateParameterSource;
 import org.apache.camel.support.CamelContextHelper;
+import org.apache.camel.support.LocalBeanRegistry;
 import org.apache.camel.support.PatternHelper;
 import org.apache.camel.support.RouteTemplateHelper;
 import org.apache.camel.util.AntPathMatcher;
@@ -708,7 +711,16 @@ public class DefaultModel implements Model {
 
     private static void addTemplateBeans(RouteTemplateContext 
routeTemplateContext, RouteTemplateDefinition target)
             throws Exception {
+        // a bean the caller has bound (TemplatedRouteBuilder or templated 
route) takes precedence over the template
+        // bean with the same name, as a parameter takes precedence over the 
default value of the template
+        Set<String> callerBeans = Collections.emptySet();
+        if (routeTemplateContext.getLocalBeanRepository() instanceof 
LocalBeanRegistry local) {
+            callerBeans = new HashSet<>(local.keys());
+        }
         for (BeanFactoryDefinition b : target.getTemplateBeans()) {
+            if (callerBeans.contains(b.getName())) {
+                continue;
+            }
             // route template beans do not directly support property 
placeholders
             // but need to use rtc.property API calls
             b.setScriptPropertyPlaceholders("false");
diff --git 
a/core/camel-core-model/src/main/java/org/apache/camel/builder/TemplatedRouteBuilder.java
 
b/core/camel-core-model/src/main/java/org/apache/camel/builder/TemplatedRouteBuilder.java
index f1f0109c4f00..41b16790589f 100644
--- 
a/core/camel-core-model/src/main/java/org/apache/camel/builder/TemplatedRouteBuilder.java
+++ 
b/core/camel-core-model/src/main/java/org/apache/camel/builder/TemplatedRouteBuilder.java
@@ -100,7 +100,8 @@ public final class TemplatedRouteBuilder {
     }
 
     /**
-     * Binds the bean to the template local repository (takes precedence over 
global beans)
+     * Binds the bean to the template local repository (takes precedence over 
global beans, and over a template bean
+     * with the same name)
      *
      * @param id   the id of the bean
      * @param bean the bean
@@ -111,7 +112,8 @@ public final class TemplatedRouteBuilder {
     }
 
     /**
-     * Binds the bean to the template local repository (takes precedence over 
global beans)
+     * Binds the bean to the template local repository (takes precedence over 
global beans, and over a template bean
+     * with the same name)
      *
      * @param id   the id of the bean
      * @param type the type of the bean to associate the binding
@@ -123,7 +125,8 @@ public final class TemplatedRouteBuilder {
     }
 
     /**
-     * Binds the bean (via a supplier) to the template local repository (takes 
precedence over global beans)
+     * Binds the bean (via a supplier) to the template local repository (takes 
precedence over global beans, and over a
+     * template bean with the same name)
      *
      * @param id   the id of the bean
      * @param type the type of the bean to associate the binding
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/builder/RouteTemplateCallerBeanTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/builder/RouteTemplateCallerBeanTest.java
new file mode 100644
index 000000000000..336e07095832
--- /dev/null
+++ 
b/core/camel-core/src/test/java/org/apache/camel/builder/RouteTemplateCallerBeanTest.java
@@ -0,0 +1,104 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.builder;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.Processor;
+import org.apache.camel.model.ModelCamelContext;
+import org.apache.camel.model.TemplatedRouteDefinition;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * A bean that the caller binds with the same name as a template bean takes 
precedence over the template bean.
+ */
+class RouteTemplateCallerBeanTest extends ContextTestSupport {
+
+    private final AtomicInteger templateGreetings = new AtomicInteger();
+
+    @Override
+    public boolean isUseRouteBuilder() {
+        return false;
+    }
+
+    @BeforeEach
+    void addTemplate() throws Exception {
+        context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() {
+                routeTemplate("myTemplate")
+                        .templateParameter("foo")
+                        .templateBean("greeting", Processor.class, rtc -> {
+                            templateGreetings.incrementAndGet();
+                            return (Processor) e -> 
e.getMessage().setBody("template");
+                        })
+                        .templateBean("suffix", Processor.class,
+                                rtc -> (Processor) e -> 
e.getMessage().setBody(e.getMessage().getBody() + "!"))
+                        .from("direct:{{foo}}")
+                        .process("greeting")
+                        .process("suffix");
+            }
+        });
+        context.start();
+    }
+
+    @Test
+    void builderBeanTakesPrecedence() {
+        TemplatedRouteBuilder.builder(context, "myTemplate")
+                .parameter("foo", "one")
+                .bean("greeting", mine())
+                .add();
+        TemplatedRouteBuilder.builder(context, "myTemplate")
+                .parameter("foo", "two")
+                .bean("greeting", Processor.class, mine())
+                .add();
+
+        assertThat(template.requestBody("direct:one", 
"World")).isEqualTo("caller!");
+        assertThat(template.requestBody("direct:two", 
"World")).isEqualTo("caller!");
+        // the template bean with the same name is not created
+        assertThat(templateGreetings.get()).isZero();
+    }
+
+    @Test
+    void templatedRouteBeanTakesPrecedence() throws Exception {
+        TemplatedRouteDefinition def = new TemplatedRouteDefinition();
+        def.setRouteTemplateRef("myTemplate");
+        def.parameter("foo", "one").bean("greeting", mine());
+        ((ModelCamelContext) context).addRouteFromTemplatedRoute(def);
+
+        assertThat(template.requestBody("direct:one", 
"World")).isEqualTo("caller!");
+        assertThat(templateGreetings.get()).isZero();
+    }
+
+    @Test
+    void configurerBeanTakesPrecedence() {
+        TemplatedRouteBuilder.builder(context, "myTemplate")
+                .parameter("foo", "one")
+                .configure(rtc -> rtc.bind("greeting", Processor.class, 
mine()))
+                .add();
+
+        assertThat(template.requestBody("direct:one", 
"World")).isEqualTo("caller!");
+    }
+
+    private static Processor mine() {
+        return e -> e.getMessage().setBody("caller");
+    }
+}
diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
index 44bcffc25a7a..2efde4c330fa 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
@@ -500,6 +500,12 @@ The 
`org.apache.camel.processor.InterceptSendToEndpointCallback` class is deprec
   id. Prior to Camel 4.23 they were accepted, and both processors had the same 
id at runtime.
 - A hardcoded node id in a route template no longer clashes with the same id 
in a regular route, as the ids of
   the route created from the template are prefixed.
+- A bean bound by `TemplatedRouteBuilder.bean` or in a `templatedRoute` is now 
used instead of a `templateBean` with
+  the same name, which is no longer created. Prior to Camel 4.23 the template 
bean replaced it when both had the same
+  type, and was used when the route looked the bean up by the type of the 
template bean (such as `process("myBean")`).
+  When the same `RouteTemplateContext` is reused for several 
`addRouteFromTemplate` calls, the template beans bound for
+  the first route are now kept as beans of the caller, so the later routes 
share these instances instead of each
+  getting new ones. Use a new `RouteTemplateContext` per route to get new 
template beans for each route.
 
 === camel-aws2-ddb
 
diff --git a/docs/user-manual/modules/ROOT/pages/route-template.adoc 
b/docs/user-manual/modules/ROOT/pages/route-template.adoc
index a89dcad18011..cb9e8cc28fcb 100644
--- a/docs/user-manual/modules/ROOT/pages/route-template.adoc
+++ b/docs/user-manual/modules/ROOT/pages/route-template.adoc
@@ -652,6 +652,12 @@ This is an advanced feature.
 
 See xref:route-template-bean-binding.adoc[Route Template Bean Binding] for 
details.
 
+A bean that is bound when creating the route (`TemplatedRouteBuilder.bean` or 
the beans of a `templatedRoute`)
+takes precedence over a `templateBean` with the same name, which is then not 
created. This allows replacing a
+template bean, for example with a mock in a test. The template beans are bound 
to the `RouteTemplateContext` too, so
+when the same `RouteTemplateContext` is reused for several 
`addRouteFromTemplate` calls, the routes after the first
+share the template beans created for the first route; use a new 
`RouteTemplateContext` per route to get new ones.
+
 == Configuring route templates when creating route (advanced)
 
 There may be some special situations where you want to be able to do some 
custom configuration/code when

Reply via email to