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 b5e06bde2e6b CAMEL-24775: camel-core - the type of a bean created by a 
script or a builder is optional, in the model, the schemas and the loaders as 
it is at runtime (#26513)
b5e06bde2e6b is described below

commit b5e06bde2e6bdbca6747a8b0b1137ab11f8db500
Author: Claus Ibsen <[email protected]>
AuthorDate: Wed Sep 16 18:22:54 2026 +0200

    CAMEL-24775: camel-core - the type of a bean created by a script or a 
builder is optional, in the model, the schemas and the loaders as it is at 
runtime (#26513)
    
    The type (class name) of a bean definition is no longer required in the 
model, camel-spring.xsd, camel-xml-io.xsd, the YAML schema and the YAML loader 
when the bean is created by a script or a builder, as it never was for the 
runtime and the Java DSL; bind() creates route template and kamelet local beans 
from a builderClass; a class or factory bean without a type fails with an 
exception naming the bean instead of a NullPointerException.
    
    Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../apache/camel/catalog/models/beanFactory.json   |  2 +-
 .../apache/camel/catalog/schemas/camel-spring.xsd  |  5 +-
 .../apache/camel/catalog/schemas/camel-xml-io.xsd  |  5 +-
 .../camel/catalog/schemas/camelYamlDsl-model.json  |  3 +-
 .../SpringTemplatedRouteScriptBeanNoTypeTest.java  | 78 ++++++++++++++++++++
 .../SpringTemplatedRouteScriptBeanNoTypeTest.xml   | 56 +++++++++++++++
 .../org/apache/camel/model/beanFactory.json        |  2 +-
 .../apache/camel/model/BeanFactoryDefinition.java  |  5 +-
 .../org/apache/camel/model/BeanModelHelper.java    | 82 ++++++++++++++++++----
 .../camel/builder/RouteTemplateLocalBeanTest.java  | 51 ++++++++++++++
 .../ROOT/pages/camel-4x-upgrade-guide-4_23.adoc    | 14 ++++
 .../apache/camel/dsl/xml/io/XmlLoadAppTest.java    | 71 +++++++++++++++++++
 .../org/apache/camel/dsl/xml/io/camel-app16.xml    | 39 ++++++++++
 .../org/apache/camel/dsl/xml/io/camel-app17.xml    | 38 ++++++++++
 .../org/apache/camel/dsl/xml/io/camel-app18.xml    | 35 +++++++++
 .../dsl/yaml/deserializers/ModelDeserializers.java |  2 +-
 .../dsl/yaml/deserializers/BeansDeserializer.java  | 12 ++--
 .../dsl/yaml/validator/YamlValidatorTest.java      |  9 +++
 .../test/resources/script-bean-without-type.yaml   | 41 +++++++++++
 .../resources/schema/camelYamlDsl-canonical.json   |  4 +-
 .../resources/schema/camelYamlDsl-model.json       |  3 +-
 .../generated/resources/schema/camelYamlDsl.json   |  4 +-
 .../org/apache/camel/dsl/yaml/BeansTest.groovy     | 16 +++++
 .../apache/camel/dsl/yaml/RouteTemplateTest.groovy | 62 ++++++++++++++++
 24 files changed, 606 insertions(+), 33 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/beanFactory.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/beanFactory.json
index 352fcac1a5fa..c1991852dc79 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/beanFactory.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/beanFactory.json
@@ -13,7 +13,7 @@
   },
   "properties": {
     "name": { "index": 0, "kind": "attribute", "displayName": "Name", "group": 
"common", "required": true, "type": "string", "javaType": "java.lang.String", 
"deprecated": false, "autowired": false, "secret": false, "description": "The 
name of the bean (bean id)." },
-    "type": { "index": 1, "kind": "attribute", "displayName": "Type", "group": 
"common", "required": true, "type": "string", "javaType": "java.lang.String", 
"deprecated": false, "autowired": false, "secret": false, "description": "The 
class name (fully qualified) of the bean." },
+    "type": { "index": 1, "kind": "attribute", "displayName": "Type", "group": 
"common", "required": false, "type": "string", "javaType": "java.lang.String", 
"deprecated": false, "autowired": false, "secret": false, "description": "The 
class name (fully qualified) of the bean. Required unless the bean is created 
by a script (scriptLanguage) or a builder (builderClass), where the type is 
optional and is then the type the bean is registered as." },
     "initMethod": { "index": 2, "kind": "attribute", "displayName": "Init 
Method", "group": "advanced", "label": "advanced", "required": false, "type": 
"string", "javaType": "java.lang.String", "deprecated": false, "autowired": 
false, "secret": false, "description": "The name of the custom initialization 
method to invoke after setting bean properties. The method must have no 
arguments, but may throw any exception." },
     "destroyMethod": { "index": 3, "kind": "attribute", "displayName": 
"Destroy Method", "group": "advanced", "label": "advanced", "required": false, 
"type": "string", "javaType": "java.lang.String", "deprecated": false, 
"autowired": false, "secret": false, "description": "The name of the custom 
destroy method to invoke on bean shutdown, such as when Camel is shutting down. 
The method must have no arguments, but may throw any exception." },
     "factoryMethod": { "index": 4, "kind": "attribute", "displayName": 
"Factory Method", "group": "advanced", "label": "advanced", "required": false, 
"type": "string", "javaType": "java.lang.String", "deprecated": false, 
"autowired": false, "secret": false, "description": "Name of method to invoke 
when creating the bean via a factory bean." },
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-spring.xsd
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-spring.xsd
index cf3e65e7ac17..7b80458c1dfe 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-spring.xsd
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-spring.xsd
@@ -3826,11 +3826,12 @@ The name of the bean (bean id).
         </xs:documentation>
       </xs:annotation>
     </xs:attribute>
-    <xs:attribute name="type" type="xs:string" use="required">
+    <xs:attribute name="type" type="xs:string">
       <xs:annotation>
         <xs:documentation xml:lang="en">
 <![CDATA[
-The class name (fully qualified) of the bean.
+The class name (fully qualified) of the bean. Required unless the bean is 
created by a script (scriptLanguage) or a
+builder (builderClass), where the type is optional and is then the type the 
bean is registered as.
 ]]>
         </xs:documentation>
       </xs:annotation>
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-xml-io.xsd
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-xml-io.xsd
index 04336c3ce441..1c8cdde98ce6 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-xml-io.xsd
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-xml-io.xsd
@@ -2936,11 +2936,12 @@ The name of the bean (bean id).
         </xs:documentation>
       </xs:annotation>
     </xs:attribute>
-    <xs:attribute name="type" type="xs:string" use="required">
+    <xs:attribute name="type" type="xs:string">
       <xs:annotation>
         <xs:documentation xml:lang="en">
 <![CDATA[
-The class name (fully qualified) of the bean.
+The class name (fully qualified) of the bean. Required unless the bean is 
created by a script (scriptLanguage) or a
+builder (builderClass), where the type is optional and is then the type the 
bean is registered as.
 ]]>
         </xs:documentation>
       </xs:annotation>
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camelYamlDsl-model.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camelYamlDsl-model.json
index 1bb90b40780e..d8c9a8704615 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camelYamlDsl-model.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camelYamlDsl-model.json
@@ -1450,9 +1450,8 @@
       }, {
         "name" : "type",
         "type" : "string",
-        "description" : "The class name (fully qualified) of the bean.",
+        "description" : "The class name (fully qualified) of the bean. 
Required unless the bean is created by a script (scriptLanguage) or a builder 
(builderClass), where the type is optional and is then the type the bean is 
registered as.",
         "title" : "Type",
-        "required" : true,
         "displayName" : "Type",
         "kind" : "attribute",
         "index" : 1,
diff --git 
a/components/camel-spring-parent/camel-spring-xml/src/test/java/org/apache/camel/spring/routebuilder/SpringTemplatedRouteScriptBeanNoTypeTest.java
 
b/components/camel-spring-parent/camel-spring-xml/src/test/java/org/apache/camel/spring/routebuilder/SpringTemplatedRouteScriptBeanNoTypeTest.java
new file mode 100644
index 000000000000..b11bb834a8fa
--- /dev/null
+++ 
b/components/camel-spring-parent/camel-spring-xml/src/test/java/org/apache/camel/spring/routebuilder/SpringTemplatedRouteScriptBeanNoTypeTest.java
@@ -0,0 +1,78 @@
+/*
+ * 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.spring.routebuilder;
+
+import org.apache.camel.Handler;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.spring.SpringTestSupport;
+import org.junit.jupiter.api.Test;
+import org.springframework.context.support.AbstractXmlApplicationContext;
+import org.springframework.context.support.ClassPathXmlApplicationContext;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * A templated route bean created by a script has no type (class name): the 
Spring XSD must accept it, as the runtime
+ * does.
+ */
+public class SpringTemplatedRouteScriptBeanNoTypeTest extends 
SpringTestSupport {
+
+    @Override
+    protected AbstractXmlApplicationContext createApplicationContext() {
+        return new ClassPathXmlApplicationContext(
+                
"org/apache/camel/spring/routebuilder/SpringTemplatedRouteScriptBeanNoTypeTest.xml");
+    }
+
+    @Test
+    public void testScriptBeanWithoutType() throws Exception {
+        assertEquals(1, context.getRouteTemplateDefinitions().size());
+        assertEquals(2, context.getRoutes().size());
+
+        getMockEndpoint("mock:barVal").expectedBodiesReceived("-> Hello 
John!");
+        template.sendBody("direct:fooVal", null);
+        MockEndpoint.assertIsSatisfied(context);
+    }
+
+    public static class MySpecialBean {
+        private String name;
+
+        public String getName() {
+            return name;
+        }
+
+        public void setName(String name) {
+            this.name = name;
+        }
+
+        @Handler
+        public String addName(String body) {
+            return String.format("%s %s!", body, name);
+        }
+    }
+
+    public static class MyScriptBean {
+
+        public MyScriptBean create() {
+            return new MyScriptBean();
+        }
+
+        @Handler
+        public String prefix() {
+            return "-> Hello";
+        }
+    }
+}
diff --git 
a/components/camel-spring-parent/camel-spring-xml/src/test/resources/org/apache/camel/spring/routebuilder/SpringTemplatedRouteScriptBeanNoTypeTest.xml
 
b/components/camel-spring-parent/camel-spring-xml/src/test/resources/org/apache/camel/spring/routebuilder/SpringTemplatedRouteScriptBeanNoTypeTest.xml
new file mode 100644
index 000000000000..978453305f5d
--- /dev/null
+++ 
b/components/camel-spring-parent/camel-spring-xml/src/test/resources/org/apache/camel/spring/routebuilder/SpringTemplatedRouteScriptBeanNoTypeTest.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    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.
+
+-->
+<beans xmlns="http://www.springframework.org/schema/beans";
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+       xsi:schemaLocation="
+            http://camel.apache.org/schema/spring 
http://camel.apache.org/schema/spring/camel-spring.xsd
+            http://www.springframework.org/schema/beans 
http://www.springframework.org/schema/beans/spring-beans.xsd";>
+
+    <camelContext id="foo" xmlns="http://camel.apache.org/schema/spring";>
+        <routeTemplate id="myTemplate" description="blah blah">
+            <templateParameter name="foo"/>
+            <templateParameter name="bar"/>
+            <route>
+                <from uri="direct:{{foo}}"/>
+                <to uri="bean:{{myScriptBean}}"/>
+                <to uri="bean:{{myBean}}"/>
+                <to uri="mock:{{bar}}"/>
+            </route>
+        </routeTemplate>
+        <templatedRoute routeTemplateRef="myTemplate" routeId="my-route">
+            <parameter name="foo" value="fooVal"/>
+            <parameter name="bar" value="barVal"/>
+            <bean name="myBean" 
type="#class:org.apache.camel.spring.routebuilder.SpringTemplatedRouteScriptBeanNoTypeTest$MySpecialBean">
+                <properties>
+                    <property key="name" value="John"/>
+                </properties>
+            </bean>
+            <!-- a bean created by a script needs no type (class name); the 
XSD must allow it -->
+            <bean name="myScriptBean" scriptLanguage="bean">
+                
<script>org.apache.camel.spring.routebuilder.SpringTemplatedRouteScriptBeanNoTypeTest$MyScriptBean?method=create</script>
+            </bean>
+        </templatedRoute>
+        <route>
+            <from uri="direct:a"/>
+            <to uri="log:foo"/>
+        </route>
+    </camelContext>
+
+</beans>
diff --git 
a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/beanFactory.json
 
b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/beanFactory.json
index 352fcac1a5fa..c1991852dc79 100644
--- 
a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/beanFactory.json
+++ 
b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/beanFactory.json
@@ -13,7 +13,7 @@
   },
   "properties": {
     "name": { "index": 0, "kind": "attribute", "displayName": "Name", "group": 
"common", "required": true, "type": "string", "javaType": "java.lang.String", 
"deprecated": false, "autowired": false, "secret": false, "description": "The 
name of the bean (bean id)." },
-    "type": { "index": 1, "kind": "attribute", "displayName": "Type", "group": 
"common", "required": true, "type": "string", "javaType": "java.lang.String", 
"deprecated": false, "autowired": false, "secret": false, "description": "The 
class name (fully qualified) of the bean." },
+    "type": { "index": 1, "kind": "attribute", "displayName": "Type", "group": 
"common", "required": false, "type": "string", "javaType": "java.lang.String", 
"deprecated": false, "autowired": false, "secret": false, "description": "The 
class name (fully qualified) of the bean. Required unless the bean is created 
by a script (scriptLanguage) or a builder (builderClass), where the type is 
optional and is then the type the bean is registered as." },
     "initMethod": { "index": 2, "kind": "attribute", "displayName": "Init 
Method", "group": "advanced", "label": "advanced", "required": false, "type": 
"string", "javaType": "java.lang.String", "deprecated": false, "autowired": 
false, "secret": false, "description": "The name of the custom initialization 
method to invoke after setting bean properties. The method must have no 
arguments, but may throw any exception." },
     "destroyMethod": { "index": 3, "kind": "attribute", "displayName": 
"Destroy Method", "group": "advanced", "label": "advanced", "required": false, 
"type": "string", "javaType": "java.lang.String", "deprecated": false, 
"autowired": false, "secret": false, "description": "The name of the custom 
destroy method to invoke on bean shutdown, such as when Camel is shutting down. 
The method must have no arguments, but may throw any exception." },
     "factoryMethod": { "index": 4, "kind": "attribute", "displayName": 
"Factory Method", "group": "advanced", "label": "advanced", "required": false, 
"type": "string", "javaType": "java.lang.String", "deprecated": false, 
"autowired": false, "secret": false, "description": "Name of method to invoke 
when creating the bean via a factory bean." },
diff --git 
a/core/camel-core-model/src/main/java/org/apache/camel/model/BeanFactoryDefinition.java
 
b/core/camel-core-model/src/main/java/org/apache/camel/model/BeanFactoryDefinition.java
index 1a18f89b7cd7..a1c17d519853 100644
--- 
a/core/camel-core-model/src/main/java/org/apache/camel/model/BeanFactoryDefinition.java
+++ 
b/core/camel-core-model/src/main/java/org/apache/camel/model/BeanFactoryDefinition.java
@@ -56,8 +56,9 @@ public class BeanFactoryDefinition<P> implements 
ResourceAware {
     @XmlAttribute(required = true)
     @Metadata(description = "The name of the bean (bean id).")
     private String name;
-    @XmlAttribute(required = true)
-    @Metadata(description = "The class name (fully qualified) of the bean.")
+    @XmlAttribute
+    @Metadata(description = "The class name (fully qualified) of the bean. 
Required unless the bean is created by a script (scriptLanguage)"
+                            + " or a builder (builderClass), where the type is 
optional and is then the type the bean is registered as.")
     private String type;
     @XmlAttribute
     @Metadata(label = "advanced",
diff --git 
a/core/camel-core-model/src/main/java/org/apache/camel/model/BeanModelHelper.java
 
b/core/camel-core-model/src/main/java/org/apache/camel/model/BeanModelHelper.java
index c11221f1c63f..0ff14540a0a3 100644
--- 
a/core/camel-core-model/src/main/java/org/apache/camel/model/BeanModelHelper.java
+++ 
b/core/camel-core-model/src/main/java/org/apache/camel/model/BeanModelHelper.java
@@ -58,31 +58,44 @@ public final class BeanModelHelper {
     public static Object newInstance(BeanFactoryDefinition def, CamelContext 
context) throws Exception {
         Object target;
 
+        boolean script = def.getScriptLanguage() != null && def.getScript() != 
null;
+        boolean viaBuilder = def.getBuilderClass() != null;
+
+        // the type (class name) is optional for a bean created by a script or 
a builder
         String type = def.getType();
-        if (!type.startsWith("#")) {
+        if (type == null && !script && !viaBuilder) {
+            throw new IllegalArgumentException(
+                    "Bean " + def.getName() + " must have a type (class name) 
unless created by a script or a builder");
+        }
+        if (type != null && !type.startsWith("#")) {
             type = "#class:" + type;
         }
 
         // script bean
-        if (def.getScriptLanguage() != null && def.getScript() != null) {
-            String script = resolveScript(context, def);
+        if (script) {
+            String text = resolveScript(context, def);
             // create bean via the script
             final Language lan = 
context.resolveLanguage(def.getScriptLanguage());
             final ScriptingLanguage slan = lan instanceof ScriptingLanguage sl 
? sl : null;
-            String fqn = def.getType();
-            if (fqn.startsWith("#class:")) {
-                fqn = fqn.substring(7);
+            final Class<?> clazz;
+            if (def.getType() != null) {
+                String fqn = def.getType();
+                if (fqn.startsWith("#class:")) {
+                    fqn = fqn.substring(7);
+                }
+                clazz = context.getClassResolver().resolveMandatoryClass(fqn);
+            } else {
+                clazz = Object.class;
             }
-            final Class<?> clazz = 
context.getClassResolver().resolveMandatoryClass(fqn);
             if (slan != null) {
                 // scripting language should be evaluated with context as 
binding
                 Map<String, Object> bindings = new HashMap<>();
                 bindings.put("context", context);
-                target = slan.evaluate(script, bindings, clazz);
+                target = slan.evaluate(text, bindings, clazz);
             } else {
                 Exchange dummy = ExchangeHelper.getDummy(context);
-                String text = 
ScriptHelper.resolveOptionalExternalScript(context, dummy, script);
-                Expression exp = lan.createExpression(text);
+                String external = 
ScriptHelper.resolveOptionalExternalScript(context, dummy, text);
+                Expression exp = lan.createExpression(external);
                 target = exp.evaluate(dummy, clazz);
             }
 
@@ -90,7 +103,7 @@ public final class BeanModelHelper {
             if (target == null) {
                 throw new NoSuchBeanException(def.getName(), "Creating bean 
using script returned null");
             }
-        } else if (def.getBuilderClass() != null) {
+        } else if (viaBuilder) {
             // builder class and method
             Class<?> clazz = 
context.getClassResolver().resolveMandatoryClass(def.getBuilderClass());
             Object builder = context.getInjector().newInstance(clazz);
@@ -129,7 +142,7 @@ public final class BeanModelHelper {
         }
 
         // do not set properties when using #type as it uses an existing 
shared bean
-        boolean setProps = !type.startsWith("#type");
+        boolean setProps = type == null || !type.startsWith("#type");
         if (setProps) {
             // set optional properties on created bean
             if (def.getProperties() != null && !def.getProperties().isEmpty()) 
{
@@ -240,6 +253,51 @@ public final class BeanModelHelper {
                     }
                 }));
             }
+        } else if (def.getBuilderClass() != null) {
+            // builder class and method, the type (class name) is optional
+            final CamelContext camelContext = 
routeTemplateContext.getCamelContext();
+            final Class<?> clazz;
+            if (def.getBeanClass() != null) {
+                clazz = def.getBeanClass();
+            } else if (def.getType() != null) {
+                String fqn = def.getType();
+                if (fqn.contains(":")) {
+                    fqn = StringHelper.after(fqn, ":");
+                }
+                clazz = 
camelContext.getClassResolver().resolveMandatoryClass(fqn);
+            } else {
+                clazz = Object.class;
+            }
+            // memorize so the bean is only created once and the local bean is 
the same
+            // if a route template refers to the local bean multiple times
+            routeTemplateContext.bind(def.getName(), clazz, 
Suppliers.memorize(() -> {
+                try {
+                    Class<?> builderClass = 
camelContext.getClassResolver().resolveMandatoryClass(def.getBuilderClass());
+                    Object builder = 
camelContext.getInjector().newInstance(builderClass);
+                    String bm = def.getBuilderMethod() != null ? 
def.getBuilderMethod() : "build";
+                    // create bean via builder and assign as target output
+                    Object local = PropertyBindingSupport.build()
+                            .withCamelContext(camelContext)
+                            .withTarget(builder)
+                            .withRemoveParameters(true)
+                            .withProperties(props)
+                            .build(Object.class, bm);
+                    // set the optional properties the builder did not take on 
the created bean
+                    if (!props.isEmpty()) {
+                        
PropertyBindingSupport.setPropertiesOnTarget(camelContext, local, props);
+                    }
+                    if (def.getInitMethod() != null) {
+                        ObjectHelper.invokeMethodSafe(def.getInitMethod(), 
local);
+                    }
+                    if (def.getDestroyMethod() != null) {
+                        
routeTemplateContext.registerDestroyMethod(def.getName(), 
def.getDestroyMethod());
+                    }
+                    return local;
+                } catch (Exception e) {
+                    throw new IllegalStateException(
+                            "Cannot create bean: " + def.getName() + " using 
builder: " + def.getBuilderClass(), e);
+                }
+            }));
         } else if (def.getBeanClass() != null || def.getType() != null) {
             String type = def.getType();
             if (type == null) {
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/builder/RouteTemplateLocalBeanTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/builder/RouteTemplateLocalBeanTest.java
index bbe7097c2c0a..ecfb6f622618 100644
--- 
a/core/camel-core/src/test/java/org/apache/camel/builder/RouteTemplateLocalBeanTest.java
+++ 
b/core/camel-core/src/test/java/org/apache/camel/builder/RouteTemplateLocalBeanTest.java
@@ -833,6 +833,42 @@ public class RouteTemplateLocalBeanTest extends 
ContextTestSupport {
         context.stop();
     }
 
+    @Test
+    public void testLocalBeanBuilderClassWithoutType() throws Exception {
+        context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() {
+                
routeTemplate("myTemplate").templateParameter("foo").templateParameter("bar")
+                        .templateBean("myBar")
+                        // a bean created by a builder needs no type (class 
name)
+                        
.builderClass("org.apache.camel.builder.RouteTemplateLocalBeanTest$BuilderThreeProcessorBuilder")
+                        .builderMethod("createProcessor")
+                        .property("prefix", "MyPrefix ")
+                        .end()
+                        .from("direct:{{foo}}")
+                        .to("bean:{{bar}}");
+            }
+        });
+
+        context.start();
+
+        TemplatedRouteBuilder.builder(context, "myTemplate")
+                .parameter("foo", "one")
+                .parameter("bar", "myBar")
+                .routeId("myRoute")
+                .add();
+
+        assertEquals(1, context.getRoutes().size());
+
+        Object out = template.requestBody("direct:one", "World");
+        assertEquals("MyPrefix Builder3 World", out);
+
+        // should not be a global bean
+        assertNull(context.getRegistry().lookupByName("myBar"));
+
+        context.stop();
+    }
+
     @Test
     public void testLocalBeanConstructorParameterInType() throws Exception {
         context.addRoutes(new RouteBuilder() {
@@ -935,6 +971,21 @@ public class RouteTemplateLocalBeanTest extends 
ContextTestSupport {
 
     }
 
+    public static class BuilderThreeProcessorBuilder {
+
+        private String prefix = "";
+
+        public void setPrefix(String prefix) {
+            this.prefix = prefix;
+        }
+
+        public BuilderThreeProcessor createProcessor() {
+            BuilderThreeProcessor answer = new BuilderThreeProcessor();
+            answer.setPrefix(prefix);
+            return answer;
+        }
+    }
+
     public Processor createBuilderProcessor() {
         return new BuilderProcessor();
     }
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 b01b9bcc8dea..819de4a90f74 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
@@ -454,6 +454,20 @@ set here, because that would reject input that parses 
today. Routes that genuine
 an external DTD or parameter entity through this converter must supply their 
own
 `SAXParserFactory`.
 
+=== camel-core - the type of a bean created by a script or a builder is 
optional
+
+The `type` (class name) of a bean definition — `bean` under `beans`, 
`templateBean` of a route
+template, `bean` of a templated route — is no longer declared as required. It 
was required since the
+attribute also selected how the bean is created; that moved to 
`scriptLanguage` in Camel 4.1, and
+a bean created by a script (`scriptLanguage` and `script`) or a builder 
(`builderClass`) needs no
+class name: the runtime registers it as the type of the object the script or 
builder returns, as
+the Java DSL `templateBean(name, language, script)` always did.
+
+The XML schemas (`camel-spring.xsd`, `camel-xml-io.xsd`) and the YAML schema 
no longer require
+`type`, so Spring XML and the YAML validator (`camel validate`) now accept 
what the runtime already
+ran. A bean that is neither scripted nor built still needs a `type`, and the 
error for a missing one
+is now an `IllegalArgumentException` naming the bean instead of a 
`NullPointerException`.
+
 === Component deprecation
 
 ==== camel-minio
diff --git 
a/dsl/camel-xml-io-dsl/src/test/java/org/apache/camel/dsl/xml/io/XmlLoadAppTest.java
 
b/dsl/camel-xml-io-dsl/src/test/java/org/apache/camel/dsl/xml/io/XmlLoadAppTest.java
index 59aa66d385ec..60e49184b919 100644
--- 
a/dsl/camel-xml-io-dsl/src/test/java/org/apache/camel/dsl/xml/io/XmlLoadAppTest.java
+++ 
b/dsl/camel-xml-io-dsl/src/test/java/org/apache/camel/dsl/xml/io/XmlLoadAppTest.java
@@ -37,6 +37,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 public class XmlLoadAppTest {
@@ -261,6 +262,76 @@ public class XmlLoadAppTest {
         }
     }
 
+    @Test
+    public void testLoadCamelAppWithBeanScriptWithoutType() throws Exception {
+        try (DefaultCamelContext context = new DefaultCamelContext()) {
+            context.start();
+
+            Resource resource = 
PluginHelper.getResourceLoader(context).resolveResource(
+                    "/org/apache/camel/dsl/xml/io/camel-app16.xml");
+
+            RoutesLoader routesLoader = PluginHelper.getRoutesLoader(context);
+            routesLoader.preParseRoute(resource, false);
+            routesLoader.loadRoutes(resource);
+
+            assertNotNull(context.getRoute("r16"), "Loaded r16 route should be 
there");
+
+            // the script created the bean without a type being declared
+            MockEndpoint y16 = context.getEndpoint("mock:y16", 
MockEndpoint.class);
+            y16.expectedBodiesReceived("Hi World from groovy Uranus");
+            context.createProducerTemplate().sendBody("direct:x16", "I'm 
Uranus");
+            y16.assertIsSatisfied();
+
+            context.stop();
+        }
+    }
+
+    @Test
+    public void testLoadCamelAppWithBeanBuilderClassWithoutType() throws 
Exception {
+        try (DefaultCamelContext context = new DefaultCamelContext()) {
+            context.start();
+
+            Resource resource = 
PluginHelper.getResourceLoader(context).resolveResource(
+                    "/org/apache/camel/dsl/xml/io/camel-app17.xml");
+
+            RoutesLoader routesLoader = PluginHelper.getRoutesLoader(context);
+            routesLoader.preParseRoute(resource, false);
+            routesLoader.loadRoutes(resource);
+
+            assertNotNull(context.getRoute("r17"), "Loaded r17 route should be 
there");
+
+            // the builder created the bean without a type being declared
+            MockEndpoint y17 = context.getEndpoint("mock:y17", 
MockEndpoint.class);
+            y17.expectedBodiesReceived("Hi World. I am Camel and 44 years 
old!");
+            context.createProducerTemplate().sendBody("direct:x17", "Hi");
+            y17.assertIsSatisfied();
+
+            context.stop();
+        }
+    }
+
+    @Test
+    public void testLoadCamelAppWithBeanWithoutTypeFails() throws Exception {
+        try (DefaultCamelContext context = new DefaultCamelContext()) {
+            context.start();
+
+            Resource resource = 
PluginHelper.getResourceLoader(context).resolveResource(
+                    "/org/apache/camel/dsl/xml/io/camel-app18.xml");
+
+            RoutesLoader routesLoader = PluginHelper.getRoutesLoader(context);
+            routesLoader.preParseRoute(resource, false);
+
+            // a bean that is neither scripted nor built has nothing to create 
it from
+            Exception e = assertThrows(Exception.class, () -> 
routesLoader.loadRoutes(resource));
+            Throwable cause = e.getCause() != null ? e.getCause() : e;
+            assertTrue(cause instanceof IllegalArgumentException, "Expected 
IllegalArgumentException but was " + cause);
+            assertEquals("Bean xml-bean-from-registry must have a type (class 
name) unless created by a script or a builder",
+                    cause.getMessage());
+
+            context.stop();
+        }
+    }
+
     @Test
     public void testLoadCamelAppWithBeanBuilderClass() throws Exception {
         try (DefaultCamelContext context = new DefaultCamelContext()) {
diff --git 
a/dsl/camel-xml-io-dsl/src/test/resources/org/apache/camel/dsl/xml/io/camel-app16.xml
 
b/dsl/camel-xml-io-dsl/src/test/resources/org/apache/camel/dsl/xml/io/camel-app16.xml
new file mode 100644
index 000000000000..2a48259921ea
--- /dev/null
+++ 
b/dsl/camel-xml-io-dsl/src/test/resources/org/apache/camel/dsl/xml/io/camel-app16.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    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.
+
+-->
+<camel xmlns="http://camel.apache.org/schema/xml-io"; 
xmlns:s="http://www.springframework.org/schema/beans";>
+
+    <!-- a bean created by a script needs no type (class name) -->
+    <bean name="xml-bean-from-registry" scriptLanguage="groovy">
+        <script>
+            b = new org.apache.camel.dsl.xml.io.beans.Greeter()
+            m = new org.apache.camel.dsl.xml.io.beans.GreeterMessage()
+            m.msg = 'Hi World from groovy'
+            b.message = m
+            return b
+        </script>
+    </bean>
+
+    <route id="r16">
+        <from uri="direct:x16"/>
+        <process ref="xml-bean-from-registry"/>
+        <to uri="mock:y16"/>
+    </route>
+
+</camel>
diff --git 
a/dsl/camel-xml-io-dsl/src/test/resources/org/apache/camel/dsl/xml/io/camel-app17.xml
 
b/dsl/camel-xml-io-dsl/src/test/resources/org/apache/camel/dsl/xml/io/camel-app17.xml
new file mode 100644
index 000000000000..d075c9bd0c7f
--- /dev/null
+++ 
b/dsl/camel-xml-io-dsl/src/test/resources/org/apache/camel/dsl/xml/io/camel-app17.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    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.
+
+-->
+<camel xmlns="http://camel.apache.org/schema/xml-io"; 
xmlns:s="http://www.springframework.org/schema/beans";>
+
+    <!-- a bean created by a builder needs no type (class name) -->
+    <bean name="xml-bean-from-registry"
+          builderClass="org.apache.camel.dsl.xml.io.beans.MyBeanBuilder" 
builderMethod="createTheBean">
+        <properties>
+            <property key="field1" value="World"/>
+            <property key="field2" value="Camel"/>
+            <property key="age" value="44"/>
+        </properties>
+    </bean>
+
+    <route id="r17">
+        <from uri="direct:x17"/>
+        <bean ref="xml-bean-from-registry"/>
+        <to uri="mock:y17"/>
+    </route>
+
+</camel>
diff --git 
a/dsl/camel-xml-io-dsl/src/test/resources/org/apache/camel/dsl/xml/io/camel-app18.xml
 
b/dsl/camel-xml-io-dsl/src/test/resources/org/apache/camel/dsl/xml/io/camel-app18.xml
new file mode 100644
index 000000000000..64ed4b2db801
--- /dev/null
+++ 
b/dsl/camel-xml-io-dsl/src/test/resources/org/apache/camel/dsl/xml/io/camel-app18.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    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.
+
+-->
+<camel xmlns="http://camel.apache.org/schema/xml-io"; 
xmlns:s="http://www.springframework.org/schema/beans";>
+
+    <!-- a bean that is neither scripted nor built needs a type -->
+    <bean name="xml-bean-from-registry">
+        <properties>
+            <property key="field1" value="World"/>
+        </properties>
+    </bean>
+
+    <route id="r18">
+        <from uri="direct:x18"/>
+        <bean ref="xml-bean-from-registry"/>
+        <to uri="mock:y18"/>
+    </route>
+
+</camel>
diff --git 
a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializers.java
 
b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializers.java
index a10bf691ca12..c911970b1d62 100644
--- 
a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializers.java
+++ 
b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializers.java
@@ -1293,7 +1293,7 @@ public final class ModelDeserializers extends 
YamlDeserializerSupport {
                     @YamlProperty(name = "script", type = "string", 
description = "The script to execute that creates the bean when using scripting 
languages.", displayName = "Script"),
                     @YamlProperty(name = "scriptLanguage", type = "string", 
description = "The script language to use when using inlined script for 
creating the bean, such as groovy, java, javascript etc.", displayName = 
"Script Language"),
                     @YamlProperty(name = "scriptPropertyPlaceholders", type = 
"boolean", defaultValue = "true", description = "Whether the script should 
support using Camel property placeholder syntax {{ }}.", displayName = "Script 
Property Placeholders"),
-                    @YamlProperty(name = "type", type = "string", required = 
true, description = "The class name (fully qualified) of the bean.", 
displayName = "Type")
+                    @YamlProperty(name = "type", type = "string", description 
= "The class name (fully qualified) of the bean. Required unless the bean is 
created by a script (scriptLanguage) or a builder (builderClass), where the 
type is optional and is then the type the bean is registered as.", displayName 
= "Type")
             }
     )
     public static class BeanFactoryDefinitionDeserializer extends 
YamlDeserializerBase<BeanFactoryDefinition> {
diff --git 
a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/BeansDeserializer.java
 
b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/BeansDeserializer.java
index b754dc3f802d..92b4ef071ea1 100644
--- 
a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/BeansDeserializer.java
+++ 
b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/BeansDeserializer.java
@@ -69,14 +69,18 @@ public class BeansDeserializer extends 
YamlDeserializerSupport implements Constr
             }
 
             ObjectHelper.notNull(bean.getName(), "The bean name must be set");
-            ObjectHelper.notNull(bean.getType(), "The bean type must be set");
-            if (!bean.getType().startsWith("#class:")) {
-                bean.setType("#class:" + bean.getType());
-            }
             if (bean.getScriptLanguage() != null || bean.getScript() != null) {
                 ObjectHelper.notNull(bean.getScriptLanguage(), "The bean 
script language must be set");
                 ObjectHelper.notNull(bean.getScript(), "The bean script must 
be set");
             }
+            boolean script = bean.getScriptLanguage() != null && 
bean.getScript() != null;
+            // the type (class name) is optional for a bean created by a 
script or a builder
+            if (!script && bean.getBuilderClass() == null) {
+                ObjectHelper.notNull(bean.getType(), "The bean type must be 
set");
+            }
+            if (bean.getType() != null && 
!bean.getType().startsWith("#class:")) {
+                bean.setType("#class:" + bean.getType());
+            }
 
             // due to yaml-dsl is pre parsing beans which gets created eager
             // and then later beans can be parsed again such as from Yaml dsl 
files
diff --git 
a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/YamlValidatorTest.java
 
b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/YamlValidatorTest.java
index bc828f50c412..a9eb2ab4c132 100644
--- 
a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/YamlValidatorTest.java
+++ 
b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/YamlValidatorTest.java
@@ -58,6 +58,15 @@ public class YamlValidatorTest {
         
Assertions.assertTrue(report.get(0).getMessage().contains("setCheese"));
     }
 
+    @Test
+    public void testScriptAndBuilderBeanWithoutType() throws Exception {
+        // CAMEL-24775: the type (class name) is optional for a bean created 
by a script or a builder, in the
+        // registry beans and in a route template, as it is for the runtime 
and the Java DSL
+        var report = validator.validate(new 
File("src/test/resources/script-bean-without-type.yaml"));
+        Assertions.assertTrue(report.isEmpty(), "A scripted or built bean 
needs no type but got: "
+                                                + report.stream().map(e -> 
e.getMessage()).toList());
+    }
+
     @Test
     public void testTypeMismatchFiltersOneOfNoise() throws Exception {
         var report = validator.validate(new 
File("src/test/resources/type-mismatch.yaml"));
diff --git 
a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/resources/script-bean-without-type.yaml
 
b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/resources/script-bean-without-type.yaml
new file mode 100644
index 000000000000..157dcc2069ea
--- /dev/null
+++ 
b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/resources/script-bean-without-type.yaml
@@ -0,0 +1,41 @@
+#
+# 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.
+#
+
+# a bean created by a script (scriptLanguage) or a builder (builderClass) 
needs no type (class name)
+- beans:
+    - name: myBean
+      scriptLanguage: groovy
+      script: |
+        new java.util.ArrayList()
+    - name: myBuiltBean
+      builderClass: com.acme.MyBeanBuilder
+      properties:
+        field1: World
+- routeTemplate:
+    id: myTemplate
+    parameters:
+      - name: region
+    beans:
+      - name: myClient
+        scriptLanguage: groovy
+        script: |
+          "hello " + rtc.getProperty("region", String.class)
+    from:
+      uri: direct:start
+      steps:
+        - to:
+            uri: "log:{{myClient}}"
diff --git 
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl-canonical.json
 
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl-canonical.json
index c9283f62401e..62ed8fb88ff3 100644
--- 
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl-canonical.json
+++ 
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl-canonical.json
@@ -682,10 +682,10 @@
           "type" : {
             "type" : "string",
             "title" : "Type",
-            "description" : "The class name (fully qualified) of the bean."
+            "description" : "The class name (fully qualified) of the bean. 
Required unless the bean is created by a script (scriptLanguage) or a builder 
(builderClass), where the type is optional and is then the type the bean is 
registered as."
           }
         },
-        "required" : [ "name", "type" ]
+        "required" : [ "name" ]
       },
       "org.apache.camel.model.CacheDefinition" : {
         "title" : "Cache",
diff --git 
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl-model.json
 
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl-model.json
index 1bb90b40780e..d8c9a8704615 100644
--- 
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl-model.json
+++ 
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl-model.json
@@ -1450,9 +1450,8 @@
       }, {
         "name" : "type",
         "type" : "string",
-        "description" : "The class name (fully qualified) of the bean.",
+        "description" : "The class name (fully qualified) of the bean. 
Required unless the bean is created by a script (scriptLanguage) or a builder 
(builderClass), where the type is optional and is then the type the bean is 
registered as.",
         "title" : "Type",
-        "required" : true,
         "displayName" : "Type",
         "kind" : "attribute",
         "index" : 1,
diff --git 
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl.json
 
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl.json
index 5a0e9ba16d5c..bfa94fc87e16 100644
--- 
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl.json
+++ 
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl.json
@@ -742,10 +742,10 @@
           "type" : {
             "type" : "string",
             "title" : "Type",
-            "description" : "The class name (fully qualified) of the bean."
+            "description" : "The class name (fully qualified) of the bean. 
Required unless the bean is created by a script (scriptLanguage) or a builder 
(builderClass), where the type is optional and is then the type the bean is 
registered as."
           }
         },
-        "required" : [ "name", "type" ]
+        "required" : [ "name" ]
       },
       "org.apache.camel.model.CacheDefinition" : {
         "title" : "Cache",
diff --git 
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/BeansTest.groovy
 
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/BeansTest.groovy
index d5e5f40af62e..6c1599cf6c17 100644
--- 
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/BeansTest.groovy
+++ 
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/BeansTest.groovy
@@ -230,6 +230,22 @@ class BeansTest extends YamlTestSupport {
         }
     }
 
+    def "beans with script without type"() {
+        when:
+        loadRoutes """
+                - beans:
+                  - name: myBean
+                    scriptLanguage: groovy
+                    script: "var b = new ${MyBean.class.name}(); b.field1 = 
'script1'; b.field2 = 'script2'; return b"
+            """
+
+        then:
+        with(context.registry.lookupByName('myBean'), MyBean) {
+            it.field1 == 'script1'
+            it.field2 == 'script2'
+        }
+    }
+
     def "beans with script property placeholder default"() {
         when:
         context.getPropertiesComponent().addInitialProperty("cheese", "gauda")
diff --git 
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/RouteTemplateTest.groovy
 
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/RouteTemplateTest.groovy
index ef9f558abb74..5079010281be 100644
--- 
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/RouteTemplateTest.groovy
+++ 
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/RouteTemplateTest.groovy
@@ -18,6 +18,7 @@ package org.apache.camel.dsl.yaml
 
 import org.apache.camel.component.mock.MockEndpoint
 import org.apache.camel.dsl.yaml.support.YamlTestSupport
+import org.apache.camel.dsl.yaml.support.model.MyBeanBuilder
 import org.apache.camel.dsl.yaml.support.model.MySetBody
 import org.apache.camel.dsl.yaml.support.model.MyUppercaseProcessor
 import org.apache.camel.impl.engine.DefaultRoute
@@ -133,6 +134,24 @@ class RouteTemplateTest extends YamlTestSupport {
                             steps:
                               - to: "direct:myId"
                               - to: "mock:result"
+                    """),
+                asResource('script-without-type', """
+                        - routeTemplate:
+                            id: "myTemplate"
+                            beans:
+                              - name: "myProcessor"
+                                scriptLanguage: "groovy"
+                                script: "new 
${MyUppercaseProcessor.class.name}()"
+                            from:
+                              uri: "direct:{{directName}}"
+                              steps:
+                                - process:
+                                    ref: "{{myProcessor}}"
+                        - from:
+                            uri: "direct:start"
+                            steps:
+                              - to: "direct:myId"
+                              - to: "mock:result"
                     """)
         ]
     }
@@ -181,6 +200,49 @@ class RouteTemplateTest extends YamlTestSupport {
         MockEndpoint.assertIsSatisfied(context)
     }
 
+    def "create template with builder bean without type"() {
+        setup:
+        loadRoutes """
+                - routeTemplate:
+                    id: "myTemplate"
+                    beans:
+                      - name: "myBean"
+                        builderClass: "${MyBeanBuilder.class.name}"
+                        builderMethod: "createTheBean"
+                        properties:
+                          field1: "builder-hello"
+                    from:
+                      uri: "direct:{{directName}}"
+                      steps:
+                        - to: "bean:{{myBean}}?method=getField1"
+                - from:
+                    uri: "direct:start"
+                    steps:
+                      - to: "direct:myId"
+                      - to: "mock:result"
+            """
+
+        withMock('mock:result') {
+            expectedMessageCount 1
+            expectedBodiesReceived 'builder-hello'
+        }
+        when:
+        context.addRouteFromTemplate('myId', 'myTemplate', ['directName': 
'myId'])
+        context.start()
+
+        withTemplate {
+            to('direct:start').withBody('hello').send()
+        }
+        then:
+        with(context.routeTemplateDefinitions[0], RouteTemplateDefinition) {
+            id == 'myTemplate'
+            templateBeans.size() == 1
+            templateBeans[0].type == null
+        }
+
+        MockEndpoint.assertIsSatisfied(context)
+    }
+
     def "create template with properties"() {
         when:
         loadRoutes """

Reply via email to