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

Croway pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-spring-boot.git

commit b689587a76a1a09374deb0eab089301348513ed2
Author: croway <[email protected]>
AuthorDate: Wed Sep 2 15:08:32 2026 +0200

    CAMEL-24501: Report starter configuration options that cannot be bound
    
    The generated starter code discarded configuration in two places, in both
    cases without a log line, so an option that never took effect looked exactly
    like one that did.
    
    The generated *ComponentConverter, *DataFormatConverter and 
*LanguageConverter
    classes resolve the bean reference that an option of a complex (object) 
type is
    configured with. They returned null for any value that did not start with #,
    and for a value naming a bean that does not exist, so a typo in the bean id
    produced a component with the option unset. The generated convert() body now
    delegates to the new BeanReferenceHelper, which resolves #bean:id, #id, a 
plain
    bean id, #autowired and #type:fqn, and throws IllegalArgumentException 
naming
    the value, the target type and the configuration prefix when the value 
cannot
    be resolved. This also drops the per-type switch, which returned null for a
    target type it did not list.
    
    The generated customizers copied the whole configuration onto the target 
with
    CamelPropertiesHelper.copyProperties, which binds with failIfNotSet=false, 
so
    an option with no matching setter on the target was dropped. The same mojo
    emits failIfNotSet=true for camel.rest.*, so the two disagreed. The 
customizers
    now call the new CamelPropertiesHelper.copyConfigurationProperties, which:
    
    - removes the options owned by the auto configuration layer itself (enabled 
and
      customizer) before binding, as they are not options on the Camel target;
    - fails with IllegalArgumentException when an option the application 
configured
      itself cannot be set;
    - logs at DEBUG when an option that only carries its catalog default cannot 
be
      set, since the target keeps its own default and there is nothing the
      application can do about it.
    
    Telling the two apart uses the Spring ConfigurationPropertySources, so a
    catalog default that has never been bindable (camel.language.simple.trim, 
for
    example, which is an option of the expression model rather than of
    SimpleLanguage) does not turn into a startup failure for every application.
    Blanket strict binding needs the catalog defaults to stop being 
materialised as
    field initializers on the configuration classes first, which is left for a
    follow-up.
    
    camel.springboot.lenient-configuration-binding=true logs an explicitly
    configured option at WARN and continues, instead of failing.
    
    The language converter template also referenced an applicationContext field 
it
    did not declare; no starter currently generates a language converter, so 
this
    was latent.
    
    Co-Authored-By: Claude Opus 5 <[email protected]>
---
 .../HttpComponentBeanReferenceBindingTest.java     |  92 ++++++++++++
 .../spring/boot/util/BeanReferenceHelper.java      | 122 ++++++++++++++++
 .../spring/boot/util/CamelPropertiesHelper.java    | 158 ++++++++++++++++++++
 .../spring/boot/util/BeanReferenceHelperTest.java  | 160 +++++++++++++++++++++
 .../CamelPropertiesHelperLenientBindingTest.java   |  63 ++++++++
 .../boot/util/CamelPropertiesHelperTest.java       | 110 +++++++++++++-
 .../maven/SpringBootAutoConfigurationMojo.java     | 160 ++++++++-------------
 .../maven/SpringBootAutoConfigurationMojoTest.java |  76 ++++++++++
 8 files changed, 838 insertions(+), 103 deletions(-)

diff --git 
a/components-starter/camel-http-starter/src/test/java/org/apache/camel/component/http/springboot/HttpComponentBeanReferenceBindingTest.java
 
b/components-starter/camel-http-starter/src/test/java/org/apache/camel/component/http/springboot/HttpComponentBeanReferenceBindingTest.java
new file mode 100644
index 00000000000..cc2859adfdd
--- /dev/null
+++ 
b/components-starter/camel-http-starter/src/test/java/org/apache/camel/component/http/springboot/HttpComponentBeanReferenceBindingTest.java
@@ -0,0 +1,92 @@
+/*
+ * 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.component.http.springboot;
+
+import org.apache.camel.support.jsse.SSLContextParameters;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.autoconfigure.AutoConfigurations;
+import 
org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.boot.test.context.runner.ApplicationContextRunner;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * A complex (object) typed option such as {@code sslContextParameters} is 
configured with a reference to a bean in the
+ * Spring application context. A value that cannot be resolved must be 
reported, not silently turned into null.
+ */
+class HttpComponentBeanReferenceBindingTest {
+
+    private final ApplicationContextRunner runner = new 
ApplicationContextRunner()
+            .withConfiguration(AutoConfigurations.of(TestConfiguration.class))
+            .withUserConfiguration(HttpComponentConverter.class);
+
+    @Test
+    void testBeanSyntaxIsResolved() {
+        
runner.withPropertyValues("camel.component.http.ssl-context-parameters=#bean:mySslContextParameters")
+                .run(context -> {
+                    assertThat(context).hasNotFailed();
+                    
assertThat(context.getBean(HttpComponentConfiguration.class).getSslContextParameters())
+                            
.isSameAs(context.getBean("mySslContextParameters"));
+                });
+    }
+
+    @Test
+    void testHashSyntaxIsResolved() {
+        
runner.withPropertyValues("camel.component.http.ssl-context-parameters=#mySslContextParameters")
+                .run(context -> {
+                    assertThat(context).hasNotFailed();
+                    
assertThat(context.getBean(HttpComponentConfiguration.class).getSslContextParameters())
+                            
.isSameAs(context.getBean("mySslContextParameters"));
+                });
+    }
+
+    @Test
+    void testPlainBeanIdIsResolved() {
+        // a plain bean id used to be silently converted to null
+        
runner.withPropertyValues("camel.component.http.ssl-context-parameters=mySslContextParameters")
+                .run(context -> {
+                    assertThat(context).hasNotFailed();
+                    
assertThat(context.getBean(HttpComponentConfiguration.class).getSslContextParameters())
+                            
.isSameAs(context.getBean("mySslContextParameters"));
+                });
+    }
+
+    @Test
+    void testUnresolvableValueIsReported() {
+        // a typo used to leave the option unset without any error
+        
runner.withPropertyValues("camel.component.http.ssl-context-parameters=#bean:mySslContextParametrs")
+                .run(context -> {
+                    assertThat(context).hasFailed();
+                    assertThat(context.getStartupFailure())
+                            .hasStackTraceContaining("mySslContextParametrs")
+                            .hasStackTraceContaining("camel.component.http");
+                });
+    }
+
+    @Configuration(proxyBeanMethods = false)
+    @EnableConfigurationProperties(HttpComponentConfiguration.class)
+    static class TestConfiguration {
+
+        @Bean(name = "mySslContextParameters")
+        SSLContextParameters mySslContextParameters() {
+            return new SSLContextParameters();
+        }
+    }
+
+}
diff --git 
a/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/util/BeanReferenceHelper.java
 
b/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/util/BeanReferenceHelper.java
new file mode 100644
index 00000000000..63de31c97e5
--- /dev/null
+++ 
b/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/util/BeanReferenceHelper.java
@@ -0,0 +1,122 @@
+/*
+ * 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.boot.util;
+
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.core.convert.TypeDescriptor;
+import org.springframework.util.ClassUtils;
+
+/**
+ * Resolves the bean references that Camel configuration options of a complex 
(object) type are configured with in
+ * <tt>application.properties</tt>, such as
+ * <tt>camel.component.netty-http.ssl-context-parameters = 
#bean:mySslContextParameters</tt>.
+ * <p/>
+ * This is used by the generated <tt>*ComponentConverter</tt>, 
<tt>*DataFormatConverter</tt> and
+ * <tt>*LanguageConverter</tt> classes in the Camel Spring Boot starters. The 
following syntaxes are supported:
+ * <ul>
+ * <li><tt>#bean:myBean</tt> - lookup the bean by its id</li>
+ * <li><tt>#myBean</tt> - lookup the bean by its id</li>
+ * <li><tt>myBean</tt> - lookup the bean by its id</li>
+ * <li><tt>#autowired</tt> - lookup the single bean of the option type</li>
+ * <li><tt>#type:com.foo.MyType</tt> - lookup the single bean of the given 
type</li>
+ * </ul>
+ * A configured value that cannot be resolved is reported by throwing an 
{@link IllegalArgumentException}, so a typo in
+ * a configuration file is not silently turned into a <tt>null</tt> value.
+ */
+public final class BeanReferenceHelper {
+
+    private static final String BEAN_PREFIX = "#bean:";
+    private static final String TYPE_PREFIX = "#type:";
+    private static final String CLASS_PREFIX = "#class:";
+    private static final String AUTOWIRED = "#autowired";
+
+    private BeanReferenceHelper() {
+    }
+
+    /**
+     * Resolves the given configured value as a bean from the Spring 
application context.
+     *
+     * @param  applicationContext
+     *                                  the Spring application context
+     * @param  source
+     *                                  the configured value, such as 
<tt>#bean:myBean</tt>
+     * @param  targetType
+     *                                  the type the option expects
+     * @param  propertyPrefix
+     *                                  the configuration prefix the option 
belongs to, such as
+     *                                  <tt>camel.component.netty-http</tt>, 
used to make the error message actionable
+     *
+     * @return                          the resolved bean, or <tt>null</tt> if 
no value was configured
+     *
+     * @throws IllegalArgumentException
+     *                                  if a value was configured but cannot 
be resolved to a bean of the target type
+     */
+    public static Object resolveBeanReference(ApplicationContext 
applicationContext, Object source,
+            TypeDescriptor targetType, String propertyPrefix) {
+        if (source == null) {
+            return null;
+        }
+        String value = source.toString().trim();
+        if (value.isEmpty()) {
+            return null;
+        }
+        Class<?> type = targetType != null ? targetType.getObjectType() : 
Object.class;
+        if (applicationContext == null) {
+            throw new IllegalArgumentException(
+                    message(value, type, propertyPrefix, "there is no Spring 
application context available"));
+        }
+        if (value.startsWith(CLASS_PREFIX)) {
+            throw new IllegalArgumentException(message(value, type, 
propertyPrefix,
+                    "the #class: syntax is not supported here, declare the 
bean in the Spring application context instead"));
+        }
+        try {
+            if (AUTOWIRED.equalsIgnoreCase(value)) {
+                return applicationContext.getBean(type);
+            }
+            if (value.startsWith(TYPE_PREFIX)) {
+                String fqn = value.substring(TYPE_PREFIX.length()).trim();
+                return applicationContext.getBean(ClassUtils.forName(fqn, 
applicationContext.getClassLoader()));
+            }
+            String id = value;
+            if (id.startsWith(BEAN_PREFIX)) {
+                id = id.substring(BEAN_PREFIX.length()).trim();
+            } else if (id.startsWith("#")) {
+                id = id.substring(1).trim();
+            }
+            if (id.isEmpty()) {
+                throw new IllegalArgumentException(message(value, type, 
propertyPrefix, "the bean id is empty"));
+            }
+            return applicationContext.getBean(id, type);
+        } catch (BeansException | ClassNotFoundException | LinkageError e) {
+            throw new IllegalArgumentException(message(value, type, 
propertyPrefix, e.getMessage()), e);
+        }
+    }
+
+    private static String message(String value, Class<?> type, String 
propertyPrefix, String reason) {
+        StringBuilder sb = new StringBuilder(256);
+        sb.append("Cannot resolve value [").append(value).append("] as a bean 
of type [").append(type.getName())
+                .append("]");
+        if (propertyPrefix != null && !propertyPrefix.isEmpty()) {
+            sb.append(" while configuring 
").append(propertyPrefix).append(".*");
+        }
+        sb.append(". Use #bean:myBeanId (or #myBeanId, or myBeanId) to refer 
to a bean in the Spring application"
+                  + " context, or #autowired or #type:com.foo.MyType to refer 
to it by type. Reason: ").append(reason);
+        return sb.toString();
+    }
+
+}
diff --git 
a/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/util/CamelPropertiesHelper.java
 
b/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/util/CamelPropertiesHelper.java
index 31d2b05cdaa..66b1601fd29 100644
--- 
a/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/util/CamelPropertiesHelper.java
+++ 
b/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/util/CamelPropertiesHelper.java
@@ -16,27 +16,174 @@
  */
 package org.apache.camel.spring.boot.util;
 
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
 import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
 import org.apache.camel.CamelContext;
 import org.apache.camel.Component;
 import org.apache.camel.PropertyBindingException;
 import org.apache.camel.spi.BeanIntrospection;
+import org.apache.camel.spi.PropertiesComponent;
 import org.apache.camel.spi.PropertyConfigurer;
 import org.apache.camel.support.PluginHelper;
 import org.apache.camel.support.PropertyBindingSupport;
 import org.apache.camel.support.service.ServiceHelper;
 import org.apache.camel.util.ObjectHelper;
+import org.apache.camel.util.StringHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import 
org.springframework.boot.context.properties.source.ConfigurationPropertyName;
+import 
org.springframework.boot.context.properties.source.ConfigurationPropertySource;
+import 
org.springframework.boot.context.properties.source.ConfigurationPropertySources;
+import org.springframework.context.ApplicationContext;
 
 /**
  * To help configuring Camel properties that have been defined in Spring Boot 
configuration files.
  */
 public final class CamelPropertiesHelper {
 
+    /**
+     * Property to turn off failing fast when an explicitly configured Spring 
Boot option cannot be set on the Camel
+     * component, data format or language it belongs to. When lenient, such an 
option is logged at WARN level and
+     * ignored, which is close to the behaviour before Camel 4.23, where it 
was dropped without any log line.
+     */
+    public static final String LENIENT_CONFIGURATION_BINDING = 
"camel.springboot.lenient-configuration-binding";
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(CamelPropertiesHelper.class);
+
+    /**
+     * Options that belong to the Spring Boot auto configuration layer itself, 
and therefore are not options on the
+     * Camel component, data format or language being configured.
+     */
+    private static final Set<String> AUTO_CONFIGURATION_OPTIONS = 
Set.of("enabled", "customizer");
+
     private CamelPropertiesHelper() {
     }
 
+    /**
+     * Copies the options from a generated Spring Boot configuration class 
onto the Camel component, data format or
+     * language it configures.
+     * <p/>
+     * The options that belong to the auto configuration layer itself 
(<tt>enabled</tt> and <tt>customizer</tt>) are
+     * removed first, as they are not options on the target bean.
+     * <p/>
+     * An option that cannot be set on the target and that the application 
configured explicitly fails fast with an
+     * {@link IllegalArgumentException}, instead of being dropped without a 
trace. An option that cannot be set and
+     * that only carries its catalog default is logged at DEBUG, as there is 
nothing the application can do about it
+     * and the target keeps its own default. Set {@link 
#LENIENT_CONFIGURATION_BINDING} to <tt>true</tt> to log an
+     * explicitly configured option at WARN and continue, instead of failing.
+     *
+     * @param camelContext
+     *                           the CamelContext
+     * @param applicationContext
+     *                           the Spring application context, used to tell 
an explicitly configured option from a
+     *                           catalog default
+     * @param propertyPrefix
+     *                           the configuration prefix of the source, such 
as <tt>camel.component.http</tt>
+     * @param source
+     *                           the Spring Boot configuration class
+     * @param target
+     *                           the Camel component, data format or language 
to configure
+     */
+    public static void copyConfigurationProperties(CamelContext camelContext, 
ApplicationContext applicationContext,
+            String propertyPrefix, Object source, Object target) {
+        ObjectHelper.notNull(camelContext, "camel context");
+        ObjectHelper.notNull(source, "source");
+        ObjectHelper.notNull(target, "target");
+
+        Map<String, Object> properties = getNonNullProperties(camelContext, 
source);
+        properties.keySet().removeIf(key -> 
AUTO_CONFIGURATION_OPTIONS.contains(key.toLowerCase(Locale.US)));
+
+        // the options that could be set are removed from the map, so what is 
left could not be set
+        doSetCamelProperties(camelContext, target, properties, false, false);
+        if (properties.isEmpty()) {
+            return;
+        }
+
+        boolean lenient = isLenientBinding(camelContext);
+        List<String> failed = new ArrayList<>();
+        for (Map.Entry<String, Object> entry : properties.entrySet()) {
+            String name = entry.getKey();
+            Object value = entry.getValue();
+            if (isExplicitlyConfigured(applicationContext, propertyPrefix, 
name)) {
+                if (lenient) {
+                    LOG.warn("Cannot configure option [{}] with value [{}] on 
[{}]. This option is ignored.",
+                            optionKey(propertyPrefix, name), value, 
ObjectHelper.classCanonicalName(target));
+                } else {
+                    failed.add(optionKey(propertyPrefix, name) + " = " + 
value);
+                }
+            } else {
+                // only the catalog default was carried, so the target keeps 
its own default
+                LOG.debug("Cannot configure option [{}] with default value 
[{}] on [{}]. This option is ignored.",
+                        optionKey(propertyPrefix, name), value, 
ObjectHelper.classCanonicalName(target));
+            }
+        }
+        if (!failed.isEmpty()) {
+            throw new IllegalArgumentException(
+                    "Cannot configure " + failed + " as the bean class [" + 
ObjectHelper.classCanonicalName(target)
+                                               + "] has no suitable setter 
method, or it is not possible to lookup a bean with that id in the"
+                                               + " Spring Boot registry. 
Remove or correct the option, or set "
+                                               + LENIENT_CONFIGURATION_BINDING 
+ "=true to ignore it.");
+        }
+    }
+
+    private static String optionKey(String propertyPrefix, String name) {
+        String dashed = StringHelper.camelCaseToDash(name);
+        return propertyPrefix != null && !propertyPrefix.isEmpty() ? 
propertyPrefix + "." + dashed : dashed;
+    }
+
+    /**
+     * Whether the application configured the given option itself, as opposed 
to the option only carrying the default
+     * value the generator took from the Camel catalog.
+     */
+    private static boolean isExplicitlyConfigured(ApplicationContext 
applicationContext, String propertyPrefix,
+            String name) {
+        if (applicationContext == null || propertyPrefix == null || 
propertyPrefix.isEmpty()) {
+            return false;
+        }
+        try {
+            ConfigurationPropertyName key
+                    = ConfigurationPropertyName.of(optionKey(propertyPrefix, 
name).toLowerCase(Locale.US));
+            for (ConfigurationPropertySource source : 
ConfigurationPropertySources
+                    .get(applicationContext.getEnvironment())) {
+                if (source.getConfigurationProperty(key) != null) {
+                    return true;
+                }
+            }
+        } catch (Exception e) {
+            LOG.debug("Cannot determine whether {}.{} was configured due to: 
{}", propertyPrefix, name,
+                    e.getMessage());
+        }
+        return false;
+    }
+
+    private static boolean isLenientBinding(CamelContext camelContext) {
+        try {
+            PropertiesComponent pc = camelContext.getPropertiesComponent();
+            if (pc != null) {
+                Optional<String> value = 
pc.resolveProperty(LENIENT_CONFIGURATION_BINDING);
+                if (value.isPresent()) {
+                    return "true".equalsIgnoreCase(value.get().trim());
+                }
+            }
+        } catch (Exception e) {
+            LOG.debug("Cannot resolve {} due to: {}. Using strict 
configuration binding.",
+                    LENIENT_CONFIGURATION_BINDING, e.getMessage());
+        }
+        return false;
+    }
+
+    /**
+     * Copies all non-null options from the source onto the target, ignoring 
any option that cannot be set.
+     *
+     * @see #copyConfigurationProperties(CamelContext, ApplicationContext, 
String, Object, Object) for copying a
+     *      generated Spring Boot configuration class onto the Camel bean it 
configures
+     */
     @SuppressWarnings({ "unchecked" })
     public static void copyProperties(CamelContext camelContext, Object 
source, Object target) {
         ObjectHelper.notNull(camelContext, "camel context");
@@ -94,6 +241,11 @@ public final class CamelPropertiesHelper {
      */
     public static boolean setCamelProperties(CamelContext context, Object 
target, Map<String, Object> properties,
             boolean failIfNotSet) {
+        return doSetCamelProperties(context, target, properties, failIfNotSet, 
true);
+    }
+
+    private static boolean doSetCamelProperties(CamelContext context, Object 
target, Map<String, Object> properties,
+            boolean failIfNotSet, boolean warnIfNotSet) {
         ObjectHelper.notNull(context, "context");
         ObjectHelper.notNull(target, "target");
         ObjectHelper.notNull(properties, "properties");
@@ -139,6 +291,12 @@ public final class CamelPropertiesHelper {
                         + "] as the bean class [" + 
ObjectHelper.classCanonicalName(target)
                         + "] has no suitable setter method, or not possible to 
lookup a bean with the id ["
                         + stringValue + "] in Spring Boot registry");
+            } else if (warnIfNotSet) {
+                LOG.warn(
+                        "Cannot configure option [{}] with value [{}] as the 
bean class [{}] has no suitable setter method,"
+                        + " or not possible to lookup a bean with the id [{}] 
in Spring Boot registry."
+                        + " This option is ignored.",
+                        name, stringValue, 
ObjectHelper.classCanonicalName(target), stringValue);
             }
         }
 
diff --git 
a/core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/util/BeanReferenceHelperTest.java
 
b/core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/util/BeanReferenceHelperTest.java
new file mode 100644
index 00000000000..ac8b807c82a
--- /dev/null
+++ 
b/core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/util/BeanReferenceHelperTest.java
@@ -0,0 +1,160 @@
+/*
+ * 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.boot.util;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import 
org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.convert.TypeDescriptor;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class BeanReferenceHelperTest {
+
+    private static final String PREFIX = "camel.component.myComponent";
+
+    private AnnotationConfigApplicationContext applicationContext;
+
+    public static class MyOption {
+    }
+
+    public static class MyOtherOption {
+    }
+
+    @Configuration
+    static class TestConfiguration {
+        @Bean(name = "myOption")
+        MyOption myOption() {
+            return new MyOption();
+        }
+
+        @Bean(name = "myOtherOption")
+        MyOtherOption myOtherOption() {
+            return new MyOtherOption();
+        }
+    }
+
+    @BeforeEach
+    public void setUp() {
+        applicationContext = new 
AnnotationConfigApplicationContext(TestConfiguration.class);
+    }
+
+    @AfterEach
+    public void tearDown() {
+        if (applicationContext != null) {
+            applicationContext.close();
+        }
+    }
+
+    private Object resolve(Object source) {
+        return BeanReferenceHelper.resolveBeanReference(applicationContext, 
source,
+                TypeDescriptor.valueOf(MyOption.class), PREFIX);
+    }
+
+    @Test
+    public void testNullAndEmptyAreNotConfigured() {
+        assertNull(resolve(null));
+        assertNull(resolve(""));
+        assertNull(resolve("   "));
+    }
+
+    @Test
+    public void testBeanSyntax() {
+        assertSame(applicationContext.getBean("myOption"), 
resolve("#bean:myOption"));
+    }
+
+    @Test
+    public void testHashSyntax() {
+        assertSame(applicationContext.getBean("myOption"), 
resolve("#myOption"));
+    }
+
+    @Test
+    public void testPlainBeanId() {
+        // a plain bean id used to be silently converted to null
+        assertSame(applicationContext.getBean("myOption"), 
resolve("myOption"));
+    }
+
+    @Test
+    public void testAutowired() {
+        assertSame(applicationContext.getBean("myOption"), 
resolve("#autowired"));
+    }
+
+    @Test
+    public void testType() {
+        assertSame(applicationContext.getBean("myOption"),
+                resolve("#type:" + MyOption.class.getName()));
+    }
+
+    @Test
+    public void testUnknownBeanFailsClosed() {
+        IllegalArgumentException e = 
assertThrows(IllegalArgumentException.class, () -> resolve("#bean:noSuchBean"));
+        assertTrue(e.getMessage().contains("#bean:noSuchBean"), 
e.getMessage());
+        assertTrue(e.getMessage().contains(MyOption.class.getName()), 
e.getMessage());
+        assertTrue(e.getMessage().contains(PREFIX), e.getMessage());
+        assertTrue(e.getMessage().contains("#bean:myBeanId"), e.getMessage());
+    }
+
+    @Test
+    public void testTypoedPlainValueFailsClosed() {
+        // this is the case that used to end up as a null value on the 
component
+        IllegalArgumentException e = 
assertThrows(IllegalArgumentException.class, () -> resolve("myOptionn"));
+        assertTrue(e.getMessage().contains("myOptionn"), e.getMessage());
+    }
+
+    @Test
+    public void testWrongBeanTypeFailsClosed() {
+        IllegalArgumentException e = 
assertThrows(IllegalArgumentException.class, () -> resolve("#myOtherOption"));
+        assertTrue(e.getMessage().contains("myOtherOption"), e.getMessage());
+        assertTrue(e.getMessage().contains(MyOption.class.getName()), 
e.getMessage());
+    }
+
+    @Test
+    public void testEmptyBeanIdFailsClosed() {
+        IllegalArgumentException e = 
assertThrows(IllegalArgumentException.class, () -> resolve("#bean:"));
+        assertTrue(e.getMessage().contains("the bean id is empty"), 
e.getMessage());
+    }
+
+    @Test
+    public void testClassSyntaxIsReported() {
+        IllegalArgumentException e = 
assertThrows(IllegalArgumentException.class,
+                () -> resolve("#class:" + MyOption.class.getName()));
+        assertTrue(e.getMessage().contains("#class: syntax is not supported"), 
e.getMessage());
+    }
+
+    @Test
+    public void testUnknownTypeFailsClosed() {
+        IllegalArgumentException e = 
assertThrows(IllegalArgumentException.class,
+                () -> resolve("#type:com.foo.DoesNotExist"));
+        assertTrue(e.getMessage().contains("com.foo.DoesNotExist"), 
e.getMessage());
+    }
+
+    @Test
+    public void testMessageMentionsTheTargetType() {
+        IllegalArgumentException e = 
assertThrows(IllegalArgumentException.class,
+                () -> 
BeanReferenceHelper.resolveBeanReference(applicationContext, "nope",
+                        TypeDescriptor.valueOf(MyOtherOption.class), PREFIX));
+        assertEquals(true, 
e.getMessage().contains(MyOtherOption.class.getName()));
+    }
+
+}
diff --git 
a/core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/util/CamelPropertiesHelperLenientBindingTest.java
 
b/core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/util/CamelPropertiesHelperLenientBindingTest.java
new file mode 100644
index 00000000000..31048904693
--- /dev/null
+++ 
b/core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/util/CamelPropertiesHelperLenientBindingTest.java
@@ -0,0 +1,63 @@
+/*
+ * 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.boot.util;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.test.spring.junit6.CamelSpringBootTest;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.ApplicationContext;
+import org.springframework.test.annotation.DirtiesContext;
+
+/**
+ * Verifies that {@link CamelPropertiesHelper#LENIENT_CONFIGURATION_BINDING} 
restores the pre Camel 4.23 behaviour of
+ * ignoring options that cannot be set on the Camel bean being configured.
+ */
+@CamelSpringBootTest
+@DirtiesContext
+@SpringBootApplication
+@SpringBootTest(
+                classes = { CamelPropertiesHelperLenientBindingTest.class },
+                properties = {
+                        "camel.springboot.lenient-configuration-binding = 
true",
+                        "camel.test.my-config.no-such-option-on-the-target = 
bar" })
+public class CamelPropertiesHelperLenientBindingTest {
+
+    @Autowired
+    ApplicationContext applicationContext;
+
+    @Autowired
+    CamelContext camelContext;
+
+    @Test
+    public void testConfiguredOptionThatCannotBeSetIsIgnoredWhenLenient() {
+        CamelPropertiesHelperTest.MyClass target = new 
CamelPropertiesHelperTest.MyClass();
+
+        CamelPropertiesHelperTest.MyDriftedConfiguration config = new 
CamelPropertiesHelperTest.MyDriftedConfiguration();
+        config.setName("Donald Duck");
+        config.setNoSuchOptionOnTheTarget("bar");
+
+        CamelPropertiesHelper.copyConfigurationProperties(camelContext, 
applicationContext,
+                CamelPropertiesHelperTest.PREFIX, config, target);
+
+        Assertions.assertEquals("Donald Duck", target.getName());
+    }
+
+}
diff --git 
a/core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/util/CamelPropertiesHelperTest.java
 
b/core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/util/CamelPropertiesHelperTest.java
index 3ef331b4d1b..d47d3eb07ff 100644
--- 
a/core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/util/CamelPropertiesHelperTest.java
+++ 
b/core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/util/CamelPropertiesHelperTest.java
@@ -20,6 +20,7 @@ import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.Map;
 import org.apache.camel.CamelContext;
+import org.apache.camel.spring.boot.ComponentConfigurationPropertiesCommon;
 import org.apache.camel.test.spring.junit6.CamelSpringBootTest;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
@@ -34,9 +35,13 @@ import org.springframework.test.annotation.DirtiesContext;
 @CamelSpringBootTest
 @DirtiesContext
 @SpringBootApplication
-@SpringBootTest(classes = { CamelPropertiesHelperTest.TestConfiguration.class 
})
+@SpringBootTest(
+                classes = { CamelPropertiesHelperTest.TestConfiguration.class 
},
+                properties = { 
"camel.test.my-config.no-such-option-on-the-target = bar" })
 public class CamelPropertiesHelperTest {
 
+    static final String PREFIX = "camel.test.my-config";
+
     @Autowired
     ApplicationContext context;
 
@@ -54,6 +59,58 @@ public class CamelPropertiesHelperTest {
     public static class MyOption {
     }
 
+    /**
+     * Mimics a generated {@code *ComponentConfiguration} class: the auto 
configuration layer options
+     * (enabled/customizer) are inherited and are not options on the Camel 
target bean.
+     */
+    public static class MyConfiguration extends 
ComponentConfigurationPropertiesCommon {
+
+        private String name;
+        private MyOption option;
+
+        public String getName() {
+            return name;
+        }
+
+        public void setName(String name) {
+            this.name = name;
+        }
+
+        public MyOption getOption() {
+            return option;
+        }
+
+        public void setOption(MyOption option) {
+            this.option = option;
+        }
+    }
+
+    /**
+     * A configuration class holding an option that does not exist on the 
target bean, which is what generator or
+     * catalog drift looks like at runtime.
+     */
+    public static class MyDriftedConfiguration extends MyConfiguration {
+
+        private String noSuchOptionOnTheTarget;
+        private String anotherOptionOnlyCarryingItsDefault;
+
+        public String getNoSuchOptionOnTheTarget() {
+            return noSuchOptionOnTheTarget;
+        }
+
+        public void setNoSuchOptionOnTheTarget(String noSuchOptionOnTheTarget) 
{
+            this.noSuchOptionOnTheTarget = noSuchOptionOnTheTarget;
+        }
+
+        public String getAnotherOptionOnlyCarryingItsDefault() {
+            return anotherOptionOnlyCarryingItsDefault;
+        }
+
+        public void setAnotherOptionOnlyCarryingItsDefault(String 
anotherOptionOnlyCarryingItsDefault) {
+            this.anotherOptionOnlyCarryingItsDefault = 
anotherOptionOnlyCarryingItsDefault;
+        }
+    }
+
     public static class MyClass {
 
         private int id;
@@ -226,6 +283,57 @@ public class CamelPropertiesHelperTest {
         Assertions.assertSame(context.getBean("myCoolOption"), 
target.getOption());
     }
 
+    @Test
+    public void 
testCopyConfigurationPropertiesIgnoresAutoConfigurationOptions() {
+        MyClass target = new MyClass();
+
+        MyConfiguration config = new MyConfiguration();
+        config.setName("Donald Duck");
+        config.setOption(context.getBean("myCoolOption", MyOption.class));
+
+        // enabled and customizer are always set on a generated configuration 
class, and must not be
+        // attempted on the target bean
+        Assertions.assertTrue(config.isEnabled());
+        Assertions.assertNotNull(config.getCustomizer());
+
+        CamelPropertiesHelper.copyConfigurationProperties(camelContext, 
context, PREFIX, config, target);
+
+        Assertions.assertEquals("Donald Duck", target.getName());
+        Assertions.assertSame(context.getBean("myCoolOption"), 
target.getOption());
+    }
+
+    @Test
+    public void 
testCopyConfigurationPropertiesFailsOnConfiguredOptionThatCannotBeSet() {
+        MyClass target = new MyClass();
+
+        MyDriftedConfiguration config = new MyDriftedConfiguration();
+        config.setName("Donald Duck");
+        // camel.test.my-config.no-such-option-on-the-target is set on the 
test application
+        config.setNoSuchOptionOnTheTarget("bar");
+
+        IllegalArgumentException e = 
Assertions.assertThrows(IllegalArgumentException.class,
+                () -> 
CamelPropertiesHelper.copyConfigurationProperties(camelContext, context, 
PREFIX, config,
+                        target));
+        
Assertions.assertTrue(e.getMessage().contains("camel.test.my-config.no-such-option-on-the-target"),
+                e.getMessage());
+        
Assertions.assertTrue(e.getMessage().contains(CamelPropertiesHelper.LENIENT_CONFIGURATION_BINDING),
+                e.getMessage());
+    }
+
+    @Test
+    public void testCopyConfigurationPropertiesIgnoresDefaultThatCannotBeSet() 
{
+        MyClass target = new MyClass();
+
+        MyDriftedConfiguration config = new MyDriftedConfiguration();
+        config.setName("Donald Duck");
+        // nothing configured this one, so it only carries a catalog default 
and must not break startup
+        config.setAnotherOptionOnlyCarryingItsDefault("false");
+
+        CamelPropertiesHelper.copyConfigurationProperties(camelContext, 
context, PREFIX, config, target);
+
+        Assertions.assertEquals("Donald Duck", target.getName());
+    }
+
     @Test
     public void testSetCamelPropertiesUnknownOptionIgnore() throws Exception {
         MyClass target = new MyClass();
diff --git 
a/tooling/camel-spring-boot-generator-maven-plugin/src/main/java/org/apache/camel/springboot/maven/SpringBootAutoConfigurationMojo.java
 
b/tooling/camel-spring-boot-generator-maven-plugin/src/main/java/org/apache/camel/springboot/maven/SpringBootAutoConfigurationMojo.java
index 554c408f7bf..f9888d27724 100644
--- 
a/tooling/camel-spring-boot-generator-maven-plugin/src/main/java/org/apache/camel/springboot/maven/SpringBootAutoConfigurationMojo.java
+++ 
b/tooling/camel-spring-boot-generator-maven-plugin/src/main/java/org/apache/camel/springboot/maven/SpringBootAutoConfigurationMojo.java
@@ -554,7 +554,7 @@ public class SpringBootAutoConfigurationMojo extends 
AbstractSpringBootGenerator
                 createComponentConfigurationSource(pkg, model, 
overrideComponentName);
                 createComponentAutoConfigurationSource(pkg, model, 
overrideComponentName, complexOptions);
                 if (complexOptions) {
-                    createComponentConverterSource(pkg, model);
+                    createComponentConverterSource(pkg, model, 
overrideComponentName);
                 }
                 createComponentSpringFactorySource(pkg, model);
             }
@@ -621,7 +621,7 @@ public class SpringBootAutoConfigurationMojo extends 
AbstractSpringBootGenerator
                 createDataFormatConfigurationSource(pkg, model, 
overrideDataFormatName);
                 createDataFormatAutoConfigurationSource(pkg, model, 
overrideDataFormatName, complexOptions);
                 if (complexOptions) {
-                    createDataFormatConverterSource(pkg, model);
+                    createDataFormatConverterSource(pkg, model, 
overrideDataFormatName);
                 }
                 createDataFormatSpringFactorySource(pkg, model);
             }
@@ -672,7 +672,7 @@ public class SpringBootAutoConfigurationMojo extends 
AbstractSpringBootGenerator
                 createLanguageConfigurationSource(pkg, model, 
overrideLanguageName);
                 createLanguageAutoConfigurationSource(pkg, model, 
overrideLanguageName, complexOptions);
                 if (complexOptions) {
-                    createLanguageConverterSource(pkg, model);
+                    createLanguageConverterSource(pkg, model, 
overrideLanguageName);
                 }
                 createLanguageSpringFactorySource(pkg, model);
             }
@@ -706,10 +706,7 @@ public class SpringBootAutoConfigurationMojo extends 
AbstractSpringBootGenerator
         }
         javaClass.getJavaDoc().setText(doc);
 
-        String prefix = "camel.component."
-                + camelCaseToDash(overrideComponentName != null ? 
overrideComponentName : model.getScheme());
-        // make sure prefix is in lower case
-        prefix = prefix.toLowerCase(Locale.US);
+        String prefix = componentPropertyPrefix(model, overrideComponentName);
         
javaClass.addAnnotation("org.springframework.boot.context.properties.ConfigurationProperties")
                 .setStringValue("prefix", prefix);
 
@@ -1162,10 +1159,7 @@ public class SpringBootAutoConfigurationMojo extends 
AbstractSpringBootGenerator
         }
         javaClass.getJavaDoc().setFullText(doc);
 
-        String prefix = "camel.dataformat."
-                + camelCaseToDash(overrideDataFormatName != null ? 
overrideDataFormatName : model.getName());
-        // make sure prefix is in lower case
-        prefix = prefix.toLowerCase(Locale.US);
+        String prefix = dataFormatPropertyPrefix(model, 
overrideDataFormatName);
         
javaClass.addAnnotation("org.springframework.boot.context.properties.ConfigurationProperties")
                 .setStringValue("prefix", prefix);
 
@@ -1269,10 +1263,7 @@ public class SpringBootAutoConfigurationMojo extends 
AbstractSpringBootGenerator
         }
         javaClass.getJavaDoc().setFullText(doc);
 
-        String prefix = "camel.language."
-                + (camelCaseToDash(overrideLanguageName != null ? 
overrideLanguageName : model.getName()));
-        // make sure prefix is in lower case
-        prefix = prefix.toLowerCase(Locale.US);
+        String prefix = languagePropertyPrefix(model, overrideLanguageName);
         
javaClass.addAnnotation("org.springframework.boot.context.properties.ConfigurationProperties")
                 .setStringValue("prefix", prefix);
 
@@ -1442,10 +1433,12 @@ public class SpringBootAutoConfigurationMojo extends 
AbstractSpringBootGenerator
         writeSourceIfChanged(javaClass, fileName, false);
     }
 
-    private void createComponentConverterSource(String packageName, 
ComponentModel model) throws MojoFailureException {
+    private void createComponentConverterSource(String packageName, 
ComponentModel model, String overrideComponentName)
+            throws MojoFailureException {
 
         final String name = 
model.getJavaType().substring(model.getJavaType().lastIndexOf(".") + 
1).replace("Component",
                 "ComponentConverter");
+        final String prefix = componentPropertyPrefix(model, 
overrideComponentName);
 
         // create converter class and write source
         JavaClass javaClass = new JavaClass(getProjectClassLoader());
@@ -1457,6 +1450,7 @@ public class SpringBootAutoConfigurationMojo extends 
AbstractSpringBootGenerator
         javaClass.addAnnotation("org.springframework.stereotype.Component");
         javaClass.addImport("java.util.LinkedHashSet");
         javaClass.addImport("java.util.Set");
+        
javaClass.addImport("org.apache.camel.spring.boot.util.BeanReferenceHelper");
         javaClass.addImport("org.springframework.core.convert.TypeDescriptor");
         
javaClass.addImport("org.springframework.core.convert.converter.GenericConverter");
 
@@ -1467,7 +1461,7 @@ public class SpringBootAutoConfigurationMojo extends 
AbstractSpringBootGenerator
         String body = createConverterPairBody(model);
         
javaClass.addMethod().setName("getConvertibleTypes").setPublic().setReturnType("Set<ConvertiblePair>")
                 .setBody(body);
-        body = createConvertBody(model);
+        body = createConvertBody(prefix);
         
javaClass.addMethod().setName("convert").setPublic().setReturnType("Object").addParameter("Object",
 "source")
                 .addParameter("TypeDescriptor", 
"sourceType").addParameter("TypeDescriptor", "targetType")
                 .setBody(body);
@@ -1479,76 +1473,32 @@ public class SpringBootAutoConfigurationMojo extends 
AbstractSpringBootGenerator
         writeComponentSpringFactorySource(packageName, name);
     }
 
-    private String createConvertBody(ComponentModel model) {
-        StringBuilder sb = new StringBuilder();
-        sb.append("if (source == null) {\n");
-        sb.append("    return null;\n");
-        sb.append("}\n");
-        sb.append("String ref = source.toString();\n");
-        sb.append("if (!ref.startsWith(\"#\")) {\n");
-        sb.append("    return null;\n");
-        sb.append("}\n");
-        sb.append("ref = ref.startsWith(\"#bean:\") ? ref.substring(6) : 
ref.substring(1);\n");
-        sb.append("switch (targetType.getName()) {\n");
-        // we need complex types only which unique types only
-        Stream<String> s = 
model.getComponentOptions().stream().filter(this::isComplexType)
-                .map(SpringBootAutoConfigurationMojo::getJavaType).distinct();
-        s.forEach(type -> {
-            String replacedType = applyTypeReplacement(type);
-            sb.append("    case \"").append(replacedType).append("\": return 
applicationContext.getBean(ref, ").append(replacedType)
-                    .append(".class);\n");
-        });
-        sb.append("}\n");
-        sb.append("return null;\n");
-        return sb.toString();
+    private static String componentPropertyPrefix(ComponentModel model, String 
overrideComponentName) {
+        return ("camel.component."
+                + camelCaseToDash(overrideComponentName != null ? 
overrideComponentName : model.getScheme()))
+                        .toLowerCase(Locale.US);
     }
 
-    private String createConvertBody(DataFormatModel model) {
-        StringBuilder sb = new StringBuilder();
-        sb.append("if (source == null) {\n");
-        sb.append("    return null;\n");
-        sb.append("}\n");
-        sb.append("String ref = source.toString();\n");
-        sb.append("if (!ref.startsWith(\"#\")) {\n");
-        sb.append("    return null;\n");
-        sb.append("}\n");
-        sb.append("ref = ref.startsWith(\"#bean:\") ? ref.substring(6) : 
ref.substring(1);\n");
-        sb.append("switch (targetType.getName()) {\n");
-        // we need complex types only which unique types only
-        Stream<String> s = 
model.getOptions().stream().filter(this::isComplexType)
-                .map(SpringBootAutoConfigurationMojo::getJavaType).distinct();
-        s.forEach(type -> {
-            String replacedType = applyTypeReplacement(type);
-            sb.append("    case \"").append(replacedType).append("\": return 
applicationContext.getBean(ref, ").append(replacedType)
-                    .append(".class);\n");
-        });
-        sb.append("}\n");
-        sb.append("return null;\n");
-        return sb.toString();
+    private static String dataFormatPropertyPrefix(DataFormatModel model, 
String overrideDataFormatName) {
+        return ("camel.dataformat."
+                + camelCaseToDash(overrideDataFormatName != null ? 
overrideDataFormatName : model.getName()))
+                        .toLowerCase(Locale.US);
     }
 
-    private String createConvertBody(LanguageModel model) {
-        StringBuilder sb = new StringBuilder();
-        sb.append("if (source == null) {\n");
-        sb.append("    return null;\n");
-        sb.append("}\n");
-        sb.append("String ref = source.toString();\n");
-        sb.append("if (!ref.startsWith(\"#\")) {\n");
-        sb.append("    return null;\n");
-        sb.append("}\n");
-        sb.append("ref = ref.startsWith(\"#bean:\") ? ref.substring(6) : 
ref.substring(1);\n");
-        sb.append("switch (targetType.getName()) {\n");
-        // we need complex types only which unique types only
-        Stream<String> s = 
model.getOptions().stream().filter(this::isComplexType)
-                .map(SpringBootAutoConfigurationMojo::getJavaType).distinct();
-        s.forEach(type -> {
-            String replacedType = applyTypeReplacement(type);
-            sb.append("    case \"").append(replacedType).append("\": return 
applicationContext.getBean(ref, ").append(replacedType)
-                    .append(".class);\n");
-        });
-        sb.append("}\n");
-        sb.append("return null;\n");
-        return sb.toString();
+    private static String languagePropertyPrefix(LanguageModel model, String 
overrideLanguageName) {
+        return ("camel.language."
+                + camelCaseToDash(overrideLanguageName != null ? 
overrideLanguageName : model.getName()))
+                        .toLowerCase(Locale.US);
+    }
+
+    /**
+     * The body of the generated converters. The value is resolved by
+     * {@code org.apache.camel.spring.boot.util.BeanReferenceHelper} which 
fails with a meaningful error when a
+     * configured value cannot be resolved, instead of silently converting it 
to null.
+     */
+    static String createConvertBody(String propertyPrefix) {
+        return "return 
BeanReferenceHelper.resolveBeanReference(applicationContext, source, 
targetType, \""
+               + propertyPrefix + "\");\n";
     }
 
     private String createConverterPairBody(ComponentModel model) {
@@ -1662,11 +1612,12 @@ public class SpringBootAutoConfigurationMojo extends 
AbstractSpringBootGenerator
         writeSourceIfChanged(javaClass, fileName, false);
     }
 
-    private void createDataFormatConverterSource(String packageName, 
DataFormatModel model)
-            throws MojoFailureException {
+    private void createDataFormatConverterSource(String packageName, 
DataFormatModel model,
+            String overrideDataFormatName) throws MojoFailureException {
 
         final String name = 
model.getJavaType().substring(model.getJavaType().lastIndexOf(".") + 1)
                 .replace("DataFormat", "DataFormatConverter");
+        final String prefix = dataFormatPropertyPrefix(model, 
overrideDataFormatName);
 
         // create converter class and write source
         JavaClass javaClass = new JavaClass(getProjectClassLoader());
@@ -1678,7 +1629,7 @@ public class SpringBootAutoConfigurationMojo extends 
AbstractSpringBootGenerator
         javaClass.addAnnotation("org.springframework.stereotype.Component");
         javaClass.addImport("java.util.LinkedHashSet");
         javaClass.addImport("java.util.Set");
-        javaClass.addImport("org.apache.camel.CamelContext");
+        
javaClass.addImport("org.apache.camel.spring.boot.util.BeanReferenceHelper");
         javaClass.addImport("org.springframework.core.convert.TypeDescriptor");
         
javaClass.addImport("org.springframework.core.convert.converter.GenericConverter");
 
@@ -1689,7 +1640,7 @@ public class SpringBootAutoConfigurationMojo extends 
AbstractSpringBootGenerator
         String body = createConverterPairBody(model);
         
javaClass.addMethod().setName("getConvertibleTypes").setPublic().setReturnType("Set<ConvertiblePair>")
                 .setBody(body);
-        body = createConvertBody(model);
+        body = createConvertBody(prefix);
         
javaClass.addMethod().setName("convert").setPublic().setReturnType("Object").addParameter("Object",
 "source")
                 .addParameter("TypeDescriptor", 
"sourceType").addParameter("TypeDescriptor", "targetType")
                 .setBody(body);
@@ -1765,33 +1716,35 @@ public class SpringBootAutoConfigurationMojo extends 
AbstractSpringBootGenerator
         writeComponentSpringFactorySource(packageName, name);
     }
 
-    private void createLanguageConverterSource(String packageName, 
LanguageModel model) throws MojoFailureException {
+    private void createLanguageConverterSource(String packageName, 
LanguageModel model, String overrideLanguageName)
+            throws MojoFailureException {
 
         final String name = 
model.getJavaType().substring(model.getJavaType().lastIndexOf(".") + 
1).replace("Language",
                 "LanguageConverter");
+        final String prefix = languagePropertyPrefix(model, 
overrideLanguageName);
 
         // create converter class and write source
         JavaClass javaClass = new JavaClass(getProjectClassLoader());
         javaClass.setPackage(packageName);
         javaClass.setName(name);
         javaClass.getJavaDoc().setFullText("Generated by 
camel-package-maven-plugin - do not edit this file!");
+        
javaClass.addAnnotation(Configuration.class).setLiteralValue("proxyBeanMethods",
 "false");
+        
javaClass.addAnnotation("org.springframework.boot.context.properties.ConfigurationPropertiesBinding");
+        javaClass.addAnnotation("org.springframework.stereotype.Component");
         javaClass.addImport("java.util.LinkedHashSet");
         javaClass.addImport("java.util.Set");
-        javaClass.addImport("org.apache.camel.CamelContext");
+        
javaClass.addImport("org.apache.camel.spring.boot.util.BeanReferenceHelper");
         javaClass.addImport("org.springframework.core.convert.TypeDescriptor");
         
javaClass.addImport("org.springframework.core.convert.converter.GenericConverter");
 
-        
javaClass.implementInterface("org.springframework.core.convert.converter.GenericConverter");
-        
javaClass.addField().setPrivate().setFinal(true).setName("camelContext")
-                .setType(loadClass("org.apache.camel.CamelContext"));
-        javaClass.addMethod().setConstructor(true).setPublic().setName(name)
-                .addParameter("org.apache.camel.CamelContext", "camelContext")
-                .setBody("this.camelContext = camelContext;\n");
+        javaClass.implementInterface("GenericConverter");
+        javaClass.addField().setPrivate().setName("applicationContext")
+                
.setType(loadClass("org.springframework.context.ApplicationContext")).addAnnotation(Autowired.class);
 
         String body = createConverterPairBody(model);
         
javaClass.addMethod().setName("getConvertibleTypes").setPublic().setReturnType("Set<ConvertiblePair>")
                 .setBody(body);
-        body = createConvertBody(model);
+        body = createConvertBody(prefix);
         
javaClass.addMethod().setName("convert").setPublic().setReturnType("Object").addParameter("Object",
 "source")
                 .addParameter("TypeDescriptor", 
"sourceType").addParameter("TypeDescriptor", "targetType")
                 .setBody(body);
@@ -1819,10 +1772,11 @@ public class SpringBootAutoConfigurationMojo extends 
AbstractSpringBootGenerator
         writeComponentSpringFactorySource(packageName, name);
     }
 
-    private static String createComponentBody(String shortJavaType, String 
name) {
+    static String createComponentBody(String shortJavaType, String name) {
         return new StringBuilder().append("return new ComponentCustomizer() 
{\n").append("    @Override\n")
                 .append("    public void configure(String name, Component 
target) {\n")
-                .append("        
CamelPropertiesHelper.copyProperties(target.getCamelContext(), configuration, 
target);\n")
+                .append("        
CamelPropertiesHelper.copyConfigurationProperties(target.getCamelContext(), 
applicationContext,\n")
+                .append("                
\"camel.component.").append(name).append("\", configuration, target);\n")
                 .append("    }\n").append("    @Override\n")
                 .append("    public boolean isEnabled(String name, Component 
target) {\n")
                 .append("        return 
HierarchicalPropertiesEvaluator.evaluate(\n")
@@ -1833,10 +1787,11 @@ public class SpringBootAutoConfigurationMojo extends 
AbstractSpringBootGenerator
                 .append("};\n").toString();
     }
 
-    private static String createDataFormatBody(String shortJavaType, String 
name) {
+    static String createDataFormatBody(String shortJavaType, String name) {
         return new StringBuilder().append("return new DataFormatCustomizer() 
{\n").append("    @Override\n")
                 .append("    public void configure(String name, DataFormat 
target) {\n")
-                .append("        
CamelPropertiesHelper.copyProperties(camelContextProvider.getObject(), 
configuration, target);\n")
+                .append("        
CamelPropertiesHelper.copyConfigurationProperties(camelContextProvider.getObject(),
 applicationContext,\n")
+                .append("                
\"camel.dataformat.").append(name).append("\", configuration, target);\n")
                 .append("    }\n").append("    @Override\n")
                 .append("    public boolean isEnabled(String name, DataFormat 
target) {\n")
                 .append("        return 
HierarchicalPropertiesEvaluator.evaluate(\n")
@@ -1847,11 +1802,12 @@ public class SpringBootAutoConfigurationMojo extends 
AbstractSpringBootGenerator
                 .append("};\n").toString();
     }
 
-    private static String createLanguageBody(String shortJavaType, String 
name) {
+    static String createLanguageBody(String shortJavaType, String name) {
         return new StringBuilder().append("return new LanguageCustomizer() 
{\n").append("    @Override\n")
                 .append("    public void configure(String name, Language 
target) {\n")
                 .append("        if (target instanceof CamelContextAware cca 
&& cca.getCamelContext() != null) {\n")
-                .append("            
CamelPropertiesHelper.copyProperties(cca.getCamelContext(), configuration, 
target);\n")
+                .append("            
CamelPropertiesHelper.copyConfigurationProperties(cca.getCamelContext(), 
applicationContext,\n")
+                .append("                    
\"camel.language.").append(name).append("\", configuration, target);\n")
                 .append("        } else {\n")
                 .append("            
org.slf4j.LoggerFactory.getLogger(getClass()).debug(\"Language {} does not 
implement CamelContextAware, skipping auto-configuration properties\", 
name);\n")
                 .append("        }\n")
diff --git 
a/tooling/camel-spring-boot-generator-maven-plugin/src/test/java/org/apache/camel/springboot/maven/SpringBootAutoConfigurationMojoTest.java
 
b/tooling/camel-spring-boot-generator-maven-plugin/src/test/java/org/apache/camel/springboot/maven/SpringBootAutoConfigurationMojoTest.java
new file mode 100644
index 00000000000..e41d3ae1f35
--- /dev/null
+++ 
b/tooling/camel-spring-boot-generator-maven-plugin/src/test/java/org/apache/camel/springboot/maven/SpringBootAutoConfigurationMojoTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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.springboot.maven;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests for the code that {@link SpringBootAutoConfigurationMojo} generates 
into the starters.
+ */
+class SpringBootAutoConfigurationMojoTest {
+
+    @Test
+    @DisplayName("Generated converters report unresolvable values instead of 
converting them to null")
+    void testConvertBodyFailsClosed() {
+        String body = 
SpringBootAutoConfigurationMojo.createConvertBody("camel.component.netty-http");
+
+        assertThat(body).isEqualTo(
+                "return 
BeanReferenceHelper.resolveBeanReference(applicationContext, source, 
targetType,"
+                                     + " \"camel.component.netty-http\");\n");
+        // the old generated body silently returned null for anything that did 
not start with #
+        assertThat(body).doesNotContain("return null");
+        assertThat(body).doesNotContain("startsWith(\"#\")");
+    }
+
+    @Test
+    @DisplayName("Generated component customizer does not silently drop 
options")
+    void testComponentBodyBindsStrictly() {
+        String body = 
SpringBootAutoConfigurationMojo.createComponentBody("NettyHttpComponent", 
"netty-http");
+
+        
assertThat(body).contains("CamelPropertiesHelper.copyConfigurationProperties(target.getCamelContext(),"
+                                  + " applicationContext,\n                
\"camel.component.netty-http\","
+                                  + " configuration, target);");
+        
assertThat(body).doesNotContain("CamelPropertiesHelper.copyProperties(");
+        assertThat(body).contains("\"camel.component.netty-http.customizer\"");
+    }
+
+    @Test
+    @DisplayName("Generated data format customizer does not silently drop 
options")
+    void testDataFormatBodyBindsStrictly() {
+        String body = 
SpringBootAutoConfigurationMojo.createDataFormatBody("JacksonDataFormat", 
"jackson");
+
+        
assertThat(body).contains("CamelPropertiesHelper.copyConfigurationProperties(camelContextProvider.getObject(),"
+                                  + " applicationContext,\n                
\"camel.dataformat.jackson\","
+                                  + " configuration, target);");
+        
assertThat(body).doesNotContain("CamelPropertiesHelper.copyProperties(");
+    }
+
+    @Test
+    @DisplayName("Generated language customizer does not silently drop 
options")
+    void testLanguageBodyBindsStrictly() {
+        String body = 
SpringBootAutoConfigurationMojo.createLanguageBody("XPathLanguage", "xpath");
+
+        
assertThat(body).contains("CamelPropertiesHelper.copyConfigurationProperties(cca.getCamelContext(),"
+                                  + " applicationContext,\n                    
\"camel.language.xpath\","
+                                  + " configuration, target);");
+        
assertThat(body).doesNotContain("CamelPropertiesHelper.copyProperties(");
+    }
+
+}

Reply via email to