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

davsclaus pushed a commit to branch fix/CAMEL-26025-api-components-bugs
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 03fc5a718b4e980a31d9c44ccf07de748ae3df33
Author: Claus Ibsen <[email protected]>
AuthorDate: Fri Oct 23 16:49:16 2026 +0200

    CAMEL-26025: camel-support - API components convert the inBody body, and an 
unknown operation of a header-selected producer fails
    
    - HeaderSelectorProducer fails for a value no method handles: generated 
InvokeOnHeaderStrategy
      classes return the new InvokeOnHeaderStrategy.NO_METHOD for an unknown 
key (regenerated for
      caffeine, consul, dynamic-router, ehcache, infinispan, servicenow, 
spring-redis and web3j)
    - AbstractApiProducer converts the inBody body to the type of the method 
parameter
    - ApiMethodPropertiesHelper ignores a header named as the prefix of the 
component
    - AbstractApiConfiguration.splitResult defaults to true, as documented
    - BodyFacts.kindOf records a Map that is also Iterable as data
    
    Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 ...CaffeineCacheProducerInvokeOnHeaderFactory.java |   2 +-
 ...eineLoadCacheProducerInvokeOnHeaderFactory.java |   2 +-
 .../caffeine/cache/CaffeineCacheProducerTest.java  |  18 ++
 .../ConsulAgentProducerInvokeOnHeaderFactory.java  |   2 +-
 ...ConsulCatalogProducerInvokeOnHeaderFactory.java |   2 +-
 ...ulCoordinatesProducerInvokeOnHeaderFactory.java |   2 +-
 .../ConsulEventProducerInvokeOnHeaderFactory.java  |   2 +-
 .../ConsulHealthProducerInvokeOnHeaderFactory.java |   2 +-
 ...onsulKeyValueProducerInvokeOnHeaderFactory.java |   2 +-
 ...PreparedQueryProducerInvokeOnHeaderFactory.java |   2 +-
 ...ConsulSessionProducerInvokeOnHeaderFactory.java |   2 +-
 .../ConsulStatusProducerInvokeOnHeaderFactory.java |   2 +-
 ...RouterControlProducerInvokeOnHeaderFactory.java |   2 +-
 .../EhcacheProducerInvokeOnHeaderFactory.java      |   2 +-
 .../GoogleSheetsConsumerSplitResultTest.java       |  86 +++++++++
 .../InfinispanProducerInvokeOnHeaderFactory.java   |   2 +-
 ...ispanEmbeddedProducerInvokeOnHeaderFactory.java |   2 +-
 ...inispanRemoteProducerInvokeOnHeaderFactory.java |   2 +-
 ...ujiServiceNowProducerInvokeOnHeaderFactory.java |   2 +-
 ...nkiServiceNowProducerInvokeOnHeaderFactory.java |   2 +-
 .../redis/RedisProducerInvokeOnHeaderFactory.java  |   2 +-
 .../web3j/Web3jProducerInvokeOnHeaderFactory.java  |   2 +-
 .../apache/camel/spi/InvokeOnHeaderStrategy.java   |  16 +-
 .../org/apache/camel/support/BodyFactsTest.java    |  19 ++
 .../camel/support/HeaderSelectorProducerTest.java  | 208 +++++++++++++++++++++
 .../component/AbstractApiConfigurationTest.java}   |  20 +-
 .../support/component/AbstractApiProducerTest.java | 200 ++++++++++++++++++++
 .../component/ApiMethodPropertiesHelperTest.java   |  16 ++
 ...support.HeaderSelectorProducerTest$BaseProducer |  18 ++
 ...upport.HeaderSelectorProducerTest$ChildProducer |  18 ++
 ...pport.HeaderSelectorProducerTest$LegacyProducer |  18 ++
 .../java/org/apache/camel/support/BodyFacts.java   |   3 +
 .../camel/support/HeaderSelectorProducer.java      |  14 +-
 .../component/AbstractApiConfiguration.java        |   2 +-
 .../support/component/AbstractApiProducer.java     |  26 ++-
 .../component/ApiMethodPropertiesHelper.java       |   4 +
 .../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc    |  32 ++++
 .../main/resources/velocity/invoke-on-header.vm    |   2 +-
 38 files changed, 715 insertions(+), 45 deletions(-)

diff --git 
a/components/camel-caffeine/src/generated/java/org/apache/camel/component/caffeine/cache/CaffeineCacheProducerInvokeOnHeaderFactory.java
 
b/components/camel-caffeine/src/generated/java/org/apache/camel/component/caffeine/cache/CaffeineCacheProducerInvokeOnHeaderFactory.java
index c4c579ece82a..afb539ddc00a 100644
--- 
a/components/camel-caffeine/src/generated/java/org/apache/camel/component/caffeine/cache/CaffeineCacheProducerInvokeOnHeaderFactory.java
+++ 
b/components/camel-caffeine/src/generated/java/org/apache/camel/component/caffeine/cache/CaffeineCacheProducerInvokeOnHeaderFactory.java
@@ -35,7 +35,7 @@ public class CaffeineCacheProducerInvokeOnHeaderFactory 
implements InvokeOnHeade
         case "PUT": target.onPut(exchange.getMessage()); return null;
         case "put_all":
         case "PUT_ALL": target.onPutAll(exchange.getMessage()); return null;
-        default: return null;
+        default: return InvokeOnHeaderStrategy.NO_METHOD;
         }
     }
 
diff --git 
a/components/camel-caffeine/src/generated/java/org/apache/camel/component/caffeine/load/CaffeineLoadCacheProducerInvokeOnHeaderFactory.java
 
b/components/camel-caffeine/src/generated/java/org/apache/camel/component/caffeine/load/CaffeineLoadCacheProducerInvokeOnHeaderFactory.java
index 2765e3f7bd52..f99c4bd114d0 100644
--- 
a/components/camel-caffeine/src/generated/java/org/apache/camel/component/caffeine/load/CaffeineLoadCacheProducerInvokeOnHeaderFactory.java
+++ 
b/components/camel-caffeine/src/generated/java/org/apache/camel/component/caffeine/load/CaffeineLoadCacheProducerInvokeOnHeaderFactory.java
@@ -35,7 +35,7 @@ public class CaffeineLoadCacheProducerInvokeOnHeaderFactory 
implements InvokeOnH
         case "PUT": target.onPut(exchange.getMessage()); return null;
         case "put_all":
         case "PUT_ALL": target.onPutAll(exchange.getMessage()); return null;
-        default: return null;
+        default: return InvokeOnHeaderStrategy.NO_METHOD;
         }
     }
 
diff --git 
a/components/camel-caffeine/src/test/java/org/apache/camel/component/caffeine/cache/CaffeineCacheProducerTest.java
 
b/components/camel-caffeine/src/test/java/org/apache/camel/component/caffeine/cache/CaffeineCacheProducerTest.java
index f225f45bbc32..58a3ecef54ff 100644
--- 
a/components/camel-caffeine/src/test/java/org/apache/camel/component/caffeine/cache/CaffeineCacheProducerTest.java
+++ 
b/components/camel-caffeine/src/test/java/org/apache/camel/component/caffeine/cache/CaffeineCacheProducerTest.java
@@ -29,6 +29,7 @@ import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -258,6 +259,23 @@ public class CaffeineCacheProducerTest extends 
CaffeineCacheTestSupport {
         });
     }
 
+    // ****************************
+    // Unknown action
+    // ****************************
+
+    @Test
+    void testUnknownActionFails() throws Exception {
+        MockEndpoint mock = getMockEndpoint("mock:result");
+        mock.expectedMessageCount(0);
+
+        Exchange exchange = 
fluentTemplate().withHeader(CaffeineConstants.ACTION, 
"NOSUCH").withBody("Hello")
+                .to("direct://start").send();
+
+        IllegalArgumentException e = 
assertInstanceOf(IllegalArgumentException.class, exchange.getException());
+        assertEquals("Unsupported value NOSUCH of header " + 
CaffeineConstants.ACTION, e.getMessage());
+        MockEndpoint.assertIsSatisfied(context);
+    }
+
     // ****************************
     // Route
     // ****************************
diff --git 
a/components/camel-consul/src/generated/java/org/apache/camel/component/consul/endpoint/ConsulAgentProducerInvokeOnHeaderFactory.java
 
b/components/camel-consul/src/generated/java/org/apache/camel/component/consul/endpoint/ConsulAgentProducerInvokeOnHeaderFactory.java
index 64e4b0852265..952cf410e234 100644
--- 
a/components/camel-consul/src/generated/java/org/apache/camel/component/consul/endpoint/ConsulAgentProducerInvokeOnHeaderFactory.java
+++ 
b/components/camel-consul/src/generated/java/org/apache/camel/component/consul/endpoint/ConsulAgentProducerInvokeOnHeaderFactory.java
@@ -31,7 +31,7 @@ public class ConsulAgentProducerInvokeOnHeaderFactory 
implements InvokeOnHeaderS
         case "REGISTER": target.invokeRegister(exchange.getMessage()); return 
null;
         case "services":
         case "SERVICES": return target.invokeServices(exchange.getMessage());
-        default: return null;
+        default: return InvokeOnHeaderStrategy.NO_METHOD;
         }
     }
 
diff --git 
a/components/camel-consul/src/generated/java/org/apache/camel/component/consul/endpoint/ConsulCatalogProducerInvokeOnHeaderFactory.java
 
b/components/camel-consul/src/generated/java/org/apache/camel/component/consul/endpoint/ConsulCatalogProducerInvokeOnHeaderFactory.java
index 049a29161c16..a4bba6f00ef3 100644
--- 
a/components/camel-consul/src/generated/java/org/apache/camel/component/consul/endpoint/ConsulCatalogProducerInvokeOnHeaderFactory.java
+++ 
b/components/camel-consul/src/generated/java/org/apache/camel/component/consul/endpoint/ConsulCatalogProducerInvokeOnHeaderFactory.java
@@ -33,7 +33,7 @@ public class ConsulCatalogProducerInvokeOnHeaderFactory 
implements InvokeOnHeade
         case "LIST_SERVICES": target.listServices(exchange.getMessage()); 
return null;
         case "register":
         case "REGISTER": target.register(exchange.getMessage()); return null;
-        default: return null;
+        default: return InvokeOnHeaderStrategy.NO_METHOD;
         }
     }
 
diff --git 
a/components/camel-consul/src/generated/java/org/apache/camel/component/consul/endpoint/ConsulCoordinatesProducerInvokeOnHeaderFactory.java
 
b/components/camel-consul/src/generated/java/org/apache/camel/component/consul/endpoint/ConsulCoordinatesProducerInvokeOnHeaderFactory.java
index 57648b71366b..5fff06739145 100644
--- 
a/components/camel-consul/src/generated/java/org/apache/camel/component/consul/endpoint/ConsulCoordinatesProducerInvokeOnHeaderFactory.java
+++ 
b/components/camel-consul/src/generated/java/org/apache/camel/component/consul/endpoint/ConsulCoordinatesProducerInvokeOnHeaderFactory.java
@@ -23,7 +23,7 @@ public class ConsulCoordinatesProducerInvokeOnHeaderFactory 
implements InvokeOnH
         case "DATACENTERS": target.datacenters(exchange.getMessage()); return 
null;
         case "nodes":
         case "NODES": target.nodes(exchange.getMessage()); return null;
-        default: return null;
+        default: return InvokeOnHeaderStrategy.NO_METHOD;
         }
     }
 
diff --git 
a/components/camel-consul/src/generated/java/org/apache/camel/component/consul/endpoint/ConsulEventProducerInvokeOnHeaderFactory.java
 
b/components/camel-consul/src/generated/java/org/apache/camel/component/consul/endpoint/ConsulEventProducerInvokeOnHeaderFactory.java
index deb2c9e450c5..6db9ea36b5df 100644
--- 
a/components/camel-consul/src/generated/java/org/apache/camel/component/consul/endpoint/ConsulEventProducerInvokeOnHeaderFactory.java
+++ 
b/components/camel-consul/src/generated/java/org/apache/camel/component/consul/endpoint/ConsulEventProducerInvokeOnHeaderFactory.java
@@ -23,7 +23,7 @@ public class ConsulEventProducerInvokeOnHeaderFactory 
implements InvokeOnHeaderS
         case "FIRE": target.fire(exchange.getMessage()); return null;
         case "list":
         case "LIST": target.list(exchange.getMessage()); return null;
-        default: return null;
+        default: return InvokeOnHeaderStrategy.NO_METHOD;
         }
     }
 
diff --git 
a/components/camel-consul/src/generated/java/org/apache/camel/component/consul/endpoint/ConsulHealthProducerInvokeOnHeaderFactory.java
 
b/components/camel-consul/src/generated/java/org/apache/camel/component/consul/endpoint/ConsulHealthProducerInvokeOnHeaderFactory.java
index 44ddabccc4e4..6b5d658845bb 100644
--- 
a/components/camel-consul/src/generated/java/org/apache/camel/component/consul/endpoint/ConsulHealthProducerInvokeOnHeaderFactory.java
+++ 
b/components/camel-consul/src/generated/java/org/apache/camel/component/consul/endpoint/ConsulHealthProducerInvokeOnHeaderFactory.java
@@ -27,7 +27,7 @@ public class ConsulHealthProducerInvokeOnHeaderFactory 
implements InvokeOnHeader
         case "SERVICE_CHECKS": target.serviceChecks(exchange.getMessage()); 
return null;
         case "service_instances":
         case "SERVICE_INSTANCES": 
target.serviceInstances(exchange.getMessage()); return null;
-        default: return null;
+        default: return InvokeOnHeaderStrategy.NO_METHOD;
         }
     }
 
diff --git 
a/components/camel-consul/src/generated/java/org/apache/camel/component/consul/endpoint/ConsulKeyValueProducerInvokeOnHeaderFactory.java
 
b/components/camel-consul/src/generated/java/org/apache/camel/component/consul/endpoint/ConsulKeyValueProducerInvokeOnHeaderFactory.java
index c793fcac1c59..7a94bb91a4c9 100644
--- 
a/components/camel-consul/src/generated/java/org/apache/camel/component/consul/endpoint/ConsulKeyValueProducerInvokeOnHeaderFactory.java
+++ 
b/components/camel-consul/src/generated/java/org/apache/camel/component/consul/endpoint/ConsulKeyValueProducerInvokeOnHeaderFactory.java
@@ -37,7 +37,7 @@ public class ConsulKeyValueProducerInvokeOnHeaderFactory 
implements InvokeOnHead
         case "PUT": target.put(exchange.getMessage()); return null;
         case "unlock":
         case "UNLOCK": target.unlock(exchange.getMessage()); return null;
-        default: return null;
+        default: return InvokeOnHeaderStrategy.NO_METHOD;
         }
     }
 
diff --git 
a/components/camel-consul/src/generated/java/org/apache/camel/component/consul/endpoint/ConsulPreparedQueryProducerInvokeOnHeaderFactory.java
 
b/components/camel-consul/src/generated/java/org/apache/camel/component/consul/endpoint/ConsulPreparedQueryProducerInvokeOnHeaderFactory.java
index 5fb2476365c6..92c006f75b3f 100644
--- 
a/components/camel-consul/src/generated/java/org/apache/camel/component/consul/endpoint/ConsulPreparedQueryProducerInvokeOnHeaderFactory.java
+++ 
b/components/camel-consul/src/generated/java/org/apache/camel/component/consul/endpoint/ConsulPreparedQueryProducerInvokeOnHeaderFactory.java
@@ -25,7 +25,7 @@ public class ConsulPreparedQueryProducerInvokeOnHeaderFactory 
implements InvokeO
         case "EXECUTE": target.execute(exchange.getMessage()); return null;
         case "get":
         case "GET": target.get(exchange.getMessage()); return null;
-        default: return null;
+        default: return InvokeOnHeaderStrategy.NO_METHOD;
         }
     }
 
diff --git 
a/components/camel-consul/src/generated/java/org/apache/camel/component/consul/endpoint/ConsulSessionProducerInvokeOnHeaderFactory.java
 
b/components/camel-consul/src/generated/java/org/apache/camel/component/consul/endpoint/ConsulSessionProducerInvokeOnHeaderFactory.java
index a8e843f100b1..30f8801121b3 100644
--- 
a/components/camel-consul/src/generated/java/org/apache/camel/component/consul/endpoint/ConsulSessionProducerInvokeOnHeaderFactory.java
+++ 
b/components/camel-consul/src/generated/java/org/apache/camel/component/consul/endpoint/ConsulSessionProducerInvokeOnHeaderFactory.java
@@ -29,7 +29,7 @@ public class ConsulSessionProducerInvokeOnHeaderFactory 
implements InvokeOnHeade
         case "LIST": target.list(exchange.getMessage()); return null;
         case "renew":
         case "RENEW": target.renew(exchange.getMessage()); return null;
-        default: return null;
+        default: return InvokeOnHeaderStrategy.NO_METHOD;
         }
     }
 
diff --git 
a/components/camel-consul/src/generated/java/org/apache/camel/component/consul/endpoint/ConsulStatusProducerInvokeOnHeaderFactory.java
 
b/components/camel-consul/src/generated/java/org/apache/camel/component/consul/endpoint/ConsulStatusProducerInvokeOnHeaderFactory.java
index e31378c8c9dc..7fcecb1871a4 100644
--- 
a/components/camel-consul/src/generated/java/org/apache/camel/component/consul/endpoint/ConsulStatusProducerInvokeOnHeaderFactory.java
+++ 
b/components/camel-consul/src/generated/java/org/apache/camel/component/consul/endpoint/ConsulStatusProducerInvokeOnHeaderFactory.java
@@ -23,7 +23,7 @@ public class ConsulStatusProducerInvokeOnHeaderFactory 
implements InvokeOnHeader
         case "LEADER": return target.invokeChecks(exchange.getMessage());
         case "peers":
         case "PEERS": return target.invokePeers(exchange.getMessage());
-        default: return null;
+        default: return InvokeOnHeaderStrategy.NO_METHOD;
         }
     }
 
diff --git 
a/components/camel-dynamic-router/src/generated/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlProducerInvokeOnHeaderFactory.java
 
b/components/camel-dynamic-router/src/generated/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlProducerInvokeOnHeaderFactory.java
index 2a3c67c4e962..2ed9e105954b 100644
--- 
a/components/camel-dynamic-router/src/generated/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlProducerInvokeOnHeaderFactory.java
+++ 
b/components/camel-dynamic-router/src/generated/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlProducerInvokeOnHeaderFactory.java
@@ -24,7 +24,7 @@ public class 
DynamicRouterControlProducerInvokeOnHeaderFactory implements Invoke
         case "subscribe": target.performSubscribe(exchange.getMessage(), 
callback); return callback;
         case "unsubscribe": target.performUnsubscribe(exchange.getMessage(), 
callback); return callback;
         case "update": target.performUpdate(exchange.getMessage(), callback); 
return callback;
-        default: return null;
+        default: return InvokeOnHeaderStrategy.NO_METHOD;
         }
     }
 
diff --git 
a/components/camel-ehcache/src/generated/java/org/apache/camel/component/ehcache/EhcacheProducerInvokeOnHeaderFactory.java
 
b/components/camel-ehcache/src/generated/java/org/apache/camel/component/ehcache/EhcacheProducerInvokeOnHeaderFactory.java
index 9a32bdebc865..4aa5d0079e27 100644
--- 
a/components/camel-ehcache/src/generated/java/org/apache/camel/component/ehcache/EhcacheProducerInvokeOnHeaderFactory.java
+++ 
b/components/camel-ehcache/src/generated/java/org/apache/camel/component/ehcache/EhcacheProducerInvokeOnHeaderFactory.java
@@ -37,7 +37,7 @@ public class EhcacheProducerInvokeOnHeaderFactory implements 
InvokeOnHeaderStrat
         case "REMOVE_ALL": target.onRemoveAll(exchange.getMessage()); return 
null;
         case "replace":
         case "REPLACE": target.onReplace(exchange.getMessage()); return null;
-        default: return null;
+        default: return InvokeOnHeaderStrategy.NO_METHOD;
         }
     }
 
diff --git 
a/components/camel-google/camel-google-sheets/src/test/java/org/apache/camel/component/google/sheets/GoogleSheetsConsumerSplitResultTest.java
 
b/components/camel-google/camel-google-sheets/src/test/java/org/apache/camel/component/google/sheets/GoogleSheetsConsumerSplitResultTest.java
new file mode 100644
index 000000000000..d0af95e07cb9
--- /dev/null
+++ 
b/components/camel-google/camel-google-sheets/src/test/java/org/apache/camel/component/google/sheets/GoogleSheetsConsumerSplitResultTest.java
@@ -0,0 +1,86 @@
+/*
+ * 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.google.sheets;
+
+import java.util.Collection;
+
+import com.google.api.client.http.javanet.NetHttpTransport;
+import com.google.api.client.json.jackson2.JacksonFactory;
+import com.google.api.services.sheets.v4.Sheets;
+import org.apache.camel.CamelContext;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class GoogleSheetsConsumerSplitResultTest {
+
+    private static final String URI
+            = 
"google-sheets://spreadsheets/get?spreadsheetId=abc&clientId=id&clientSecret=secret&refreshToken=token";
+
+    @Test
+    void shouldSplitResultByDefault() throws Exception {
+        try (CamelContext context = createContext()) {
+            GoogleSheetsConsumer consumer = createConsumer(context, URI);
+            assertTrue(consumer.isSplitResult());
+        }
+    }
+
+    @Test
+    void shouldNotSplitResultWhenTurnedOff() throws Exception {
+        try (CamelContext context = createContext()) {
+            GoogleSheetsConsumer consumer = createConsumer(context, URI + 
"&splitResult=false");
+            assertFalse(consumer.isSplitResult());
+        }
+    }
+
+    private static CamelContext createContext() {
+        CamelContext context = new DefaultCamelContext();
+        context.getComponent("google-sheets", 
GoogleSheetsComponent.class).setClientFactory(new TestClientFactory());
+        context.start();
+        return context;
+    }
+
+    private static GoogleSheetsConsumer createConsumer(CamelContext context, 
String uri) throws Exception {
+        GoogleSheetsEndpoint endpoint = context.getEndpoint(uri, 
GoogleSheetsEndpoint.class);
+        return (GoogleSheetsConsumer) endpoint.createConsumer(exchange -> {
+        });
+    }
+
+    private static final class TestClientFactory implements 
GoogleSheetsClientFactory {
+
+        @Override
+        public Sheets makeClient(
+                String clientId, String clientSecret, Collection<String> 
scopes, String applicationName,
+                String refreshToken, String accessToken) {
+            return newClient();
+        }
+
+        @Override
+        public Sheets makeClient(
+                CamelContext camelContext, String serviceAccountKey, 
Collection<String> scopes, String applicationName,
+                String delegate) {
+            return newClient();
+        }
+
+        private static Sheets newClient() {
+            return new Sheets.Builder(new NetHttpTransport(), new 
JacksonFactory(), null).setApplicationName("test")
+                    .build();
+        }
+    }
+}
diff --git 
a/components/camel-infinispan/camel-infinispan-common/src/generated/java/org/apache/camel/component/infinispan/InfinispanProducerInvokeOnHeaderFactory.java
 
b/components/camel-infinispan/camel-infinispan-common/src/generated/java/org/apache/camel/component/infinispan/InfinispanProducerInvokeOnHeaderFactory.java
index 42a218a48f70..4fa90ba5b358 100644
--- 
a/components/camel-infinispan/camel-infinispan-common/src/generated/java/org/apache/camel/component/infinispan/InfinispanProducerInvokeOnHeaderFactory.java
+++ 
b/components/camel-infinispan/camel-infinispan-common/src/generated/java/org/apache/camel/component/infinispan/InfinispanProducerInvokeOnHeaderFactory.java
@@ -57,7 +57,7 @@ public class InfinispanProducerInvokeOnHeaderFactory 
implements InvokeOnHeaderSt
         case "REPLACEASYNC": target.onReplaceAsync(exchange.getMessage()); 
return null;
         case "size":
         case "SIZE": target.onSize(exchange.getMessage()); return null;
-        default: return null;
+        default: return InvokeOnHeaderStrategy.NO_METHOD;
         }
     }
 
diff --git 
a/components/camel-infinispan/camel-infinispan-embedded/src/generated/java/org/apache/camel/component/infinispan/embedded/InfinispanEmbeddedProducerInvokeOnHeaderFactory.java
 
b/components/camel-infinispan/camel-infinispan-embedded/src/generated/java/org/apache/camel/component/infinispan/embedded/InfinispanEmbeddedProducerInvokeOnHeaderFactory.java
index 05d507863615..8e0ee025fe46 100644
--- 
a/components/camel-infinispan/camel-infinispan-embedded/src/generated/java/org/apache/camel/component/infinispan/embedded/InfinispanEmbeddedProducerInvokeOnHeaderFactory.java
+++ 
b/components/camel-infinispan/camel-infinispan-embedded/src/generated/java/org/apache/camel/component/infinispan/embedded/InfinispanEmbeddedProducerInvokeOnHeaderFactory.java
@@ -23,7 +23,7 @@ public class InfinispanEmbeddedProducerInvokeOnHeaderFactory 
implements InvokeOn
         case "QUERY": target.onQuery(exchange.getMessage()); return null;
         case "stats":
         case "STATS": target.onStats(exchange.getMessage()); return null;
-        default: return null;
+        default: return InvokeOnHeaderStrategy.NO_METHOD;
         }
     }
 
diff --git 
a/components/camel-infinispan/camel-infinispan/src/generated/java/org/apache/camel/component/infinispan/remote/InfinispanRemoteProducerInvokeOnHeaderFactory.java
 
b/components/camel-infinispan/camel-infinispan/src/generated/java/org/apache/camel/component/infinispan/remote/InfinispanRemoteProducerInvokeOnHeaderFactory.java
index 3281e9823b17..bd0d1ebab0c0 100644
--- 
a/components/camel-infinispan/camel-infinispan/src/generated/java/org/apache/camel/component/infinispan/remote/InfinispanRemoteProducerInvokeOnHeaderFactory.java
+++ 
b/components/camel-infinispan/camel-infinispan/src/generated/java/org/apache/camel/component/infinispan/remote/InfinispanRemoteProducerInvokeOnHeaderFactory.java
@@ -23,7 +23,7 @@ public class InfinispanRemoteProducerInvokeOnHeaderFactory 
implements InvokeOnHe
         case "QUERY": target.onQuery(exchange.getMessage()); return null;
         case "stats":
         case "STATS": target.onStats(exchange.getMessage()); return null;
-        default: return null;
+        default: return InvokeOnHeaderStrategy.NO_METHOD;
         }
     }
 
diff --git 
a/components/camel-servicenow/camel-servicenow-component/src/generated/java/org/apache/camel/component/servicenow/releases/fuji/FujiServiceNowProducerInvokeOnHeaderFactory.java
 
b/components/camel-servicenow/camel-servicenow-component/src/generated/java/org/apache/camel/component/servicenow/releases/fuji/FujiServiceNowProducerInvokeOnHeaderFactory.java
index 419cd65b568d..08638ca151e9 100644
--- 
a/components/camel-servicenow/camel-servicenow-component/src/generated/java/org/apache/camel/component/servicenow/releases/fuji/FujiServiceNowProducerInvokeOnHeaderFactory.java
+++ 
b/components/camel-servicenow/camel-servicenow-component/src/generated/java/org/apache/camel/component/servicenow/releases/fuji/FujiServiceNowProducerInvokeOnHeaderFactory.java
@@ -22,7 +22,7 @@ public class FujiServiceNowProducerInvokeOnHeaderFactory 
implements InvokeOnHead
         case "aggregate": target.invokeProcessor2(exchange); return null;
         case "import": target.invokeProcessor3(exchange); return null;
         case "table": target.invokeProcessor1(exchange); return null;
-        default: return null;
+        default: return InvokeOnHeaderStrategy.NO_METHOD;
         }
     }
 
diff --git 
a/components/camel-servicenow/camel-servicenow-component/src/generated/java/org/apache/camel/component/servicenow/releases/helsinki/HelsinkiServiceNowProducerInvokeOnHeaderFactory.java
 
b/components/camel-servicenow/camel-servicenow-component/src/generated/java/org/apache/camel/component/servicenow/releases/helsinki/HelsinkiServiceNowProducerInvokeOnHeaderFactory.java
index 084dfb7f2994..cd2325e5bedb 100644
--- 
a/components/camel-servicenow/camel-servicenow-component/src/generated/java/org/apache/camel/component/servicenow/releases/helsinki/HelsinkiServiceNowProducerInvokeOnHeaderFactory.java
+++ 
b/components/camel-servicenow/camel-servicenow-component/src/generated/java/org/apache/camel/component/servicenow/releases/helsinki/HelsinkiServiceNowProducerInvokeOnHeaderFactory.java
@@ -29,7 +29,7 @@ public class HelsinkiServiceNowProducerInvokeOnHeaderFactory 
implements InvokeOn
         case "service_catalog_categories": target.invokeProcessor10(exchange); 
return null;
         case "service_catalog_items": target.invokeProcessor8(exchange); 
return null;
         case "table": target.invokeProcessor1(exchange); return null;
-        default: return null;
+        default: return InvokeOnHeaderStrategy.NO_METHOD;
         }
     }
 
diff --git 
a/components/camel-spring-parent/camel-spring-redis/src/generated/java/org/apache/camel/component/redis/RedisProducerInvokeOnHeaderFactory.java
 
b/components/camel-spring-parent/camel-spring-redis/src/generated/java/org/apache/camel/component/redis/RedisProducerInvokeOnHeaderFactory.java
index 8c5f4f079011..c9e5f6392c41 100644
--- 
a/components/camel-spring-parent/camel-spring-redis/src/generated/java/org/apache/camel/component/redis/RedisProducerInvokeOnHeaderFactory.java
+++ 
b/components/camel-spring-parent/camel-spring-redis/src/generated/java/org/apache/camel/component/redis/RedisProducerInvokeOnHeaderFactory.java
@@ -231,7 +231,7 @@ public class RedisProducerInvokeOnHeaderFactory implements 
InvokeOnHeaderStrateg
         case "ZREVRANK": return target.invokeZrevrank(exchange);
         case "zunionstore":
         case "ZUNIONSTORE": target.invokeZunionstore(exchange); return null;
-        default: return null;
+        default: return InvokeOnHeaderStrategy.NO_METHOD;
         }
     }
 
diff --git 
a/components/camel-web3j/src/generated/java/org/apache/camel/component/web3j/Web3jProducerInvokeOnHeaderFactory.java
 
b/components/camel-web3j/src/generated/java/org/apache/camel/component/web3j/Web3jProducerInvokeOnHeaderFactory.java
index 11879df19bcd..a52357fdd6e0 100644
--- 
a/components/camel-web3j/src/generated/java/org/apache/camel/component/web3j/Web3jProducerInvokeOnHeaderFactory.java
+++ 
b/components/camel-web3j/src/generated/java/org/apache/camel/component/web3j/Web3jProducerInvokeOnHeaderFactory.java
@@ -149,7 +149,7 @@ public class Web3jProducerInvokeOnHeaderFactory implements 
InvokeOnHeaderStrateg
         case "WEB3_SHA3": target.web3Sha3(exchange.getMessage()); return null;
         case "ethprotocolversion":
         case "ethProtocolVersion": 
target.ethProtocolVersion(exchange.getMessage()); return null;
-        default: return null;
+        default: return InvokeOnHeaderStrategy.NO_METHOD;
         }
     }
 
diff --git 
a/core/camel-api/src/main/java/org/apache/camel/spi/InvokeOnHeaderStrategy.java 
b/core/camel-api/src/main/java/org/apache/camel/spi/InvokeOnHeaderStrategy.java
index 855d441693e4..ddb247d2890b 100644
--- 
a/core/camel-api/src/main/java/org/apache/camel/spi/InvokeOnHeaderStrategy.java
+++ 
b/core/camel-api/src/main/java/org/apache/camel/spi/InvokeOnHeaderStrategy.java
@@ -30,6 +30,19 @@ import org.jspecify.annotations.Nullable;
  */
 public interface InvokeOnHeaderStrategy {
 
+    /**
+     * Returned by {@link #invoke(Object, String, Exchange, AsyncCallback)} 
when there is no method for the key, so the
+     * caller can tell an unknown key from a void method, which returns 
<tt>null</tt>.
+     *
+     * @since 4.24
+     */
+    Object NO_METHOD = new Object() {
+        @Override
+        public String toString() {
+            return "NO_METHOD";
+        }
+    };
+
     /**
      * Invoke the method based on the header key
      *
@@ -38,7 +51,8 @@ public interface InvokeOnHeaderStrategy {
      * @param  exchange  the exchange
      * @param  callback  the async callback
      * @return           option response from invoking the method, or 
<tt>null</tt> if the method is void if a value is
-     *                   returned, then this value is stored as result on the 
message body.
+     *                   returned, then this value is stored as result on the 
message body. {@link #NO_METHOD} when
+     *                   there is no method for the key.
      * @throws Exception is thrown if error invoking the method.
      */
     @Nullable
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/support/BodyFactsTest.java 
b/core/camel-core/src/test/java/org/apache/camel/support/BodyFactsTest.java
index 7acf6d0219fd..35b821925785 100644
--- a/core/camel-core/src/test/java/org/apache/camel/support/BodyFactsTest.java
+++ b/core/camel-core/src/test/java/org/apache/camel/support/BodyFactsTest.java
@@ -19,6 +19,7 @@ package org.apache.camel.support;
 import java.io.ByteArrayInputStream;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
+import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -161,6 +162,24 @@ public class BodyFactsTest extends ContextTestSupport {
         assertEquals(Kind.COLLECTION, BodyFacts.kindOf(List.of(1, 
2).iterator()));
     }
 
+    @Test
+    public void testAMapThatIsIterableIsData() {
+        // such as the response of solr (a SimpleOrderedMap), which describe 
calls a Map too
+        IterableMap map = new IterableMap();
+        map.put("id", 1);
+
+        assertEquals(Kind.DATA, BodyFacts.kindOf(map));
+        assertTrue(BodyFacts.describe(exchange(map)).contains("a Map 
(IterableMap)"), BodyFacts.describe(exchange(map)));
+    }
+
+    private static final class IterableMap extends LinkedHashMap<String, 
Object>
+            implements Iterable<Map.Entry<String, Object>> {
+        @Override
+        public Iterator<Map.Entry<String, Object>> iterator() {
+            return entrySet().iterator();
+        }
+    }
+
     @Test
     public void testTheDataFormatOfAContentType() {
         assertEquals("json", BodyFacts.dataFormatOf("application/json; 
charset=UTF-8"));
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/support/HeaderSelectorProducerTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/support/HeaderSelectorProducerTest.java
new file mode 100644
index 000000000000..4c8ff585c9e2
--- /dev/null
+++ 
b/core/camel-core/src/test/java/org/apache/camel/support/HeaderSelectorProducerTest.java
@@ -0,0 +1,208 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.support;
+
+import org.apache.camel.AsyncCallback;
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.Endpoint;
+import org.apache.camel.Exchange;
+import org.apache.camel.Message;
+import org.apache.camel.Producer;
+import org.apache.camel.spi.InvokeOnHeaderStrategy;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+/**
+ * A producer that picks its method by a header tells an unknown value from a 
void method: the strategies return
+ * {@link InvokeOnHeaderStrategy#NO_METHOD} for a value they have no method 
for.
+ */
+public class HeaderSelectorProducerTest extends ContextTestSupport {
+
+    private static final String HEADER = "MyAction";
+
+    @Test
+    public void testMethodWithResult() throws Exception {
+        Exchange exchange = process(new ChildProducer(endpoint(), null), 
"HELLO", "World");
+
+        assertNull(exchange.getException());
+        assertEquals("Hello World", exchange.getMessage().getBody());
+    }
+
+    @Test
+    public void testMethodOfTheParentStrategy() throws Exception {
+        Exchange exchange = process(new ChildProducer(endpoint(), null), 
"BASE", "World");
+
+        assertNull(exchange.getException());
+        assertEquals("Base World", exchange.getMessage().getBody());
+    }
+
+    @Test
+    public void testVoidMethodDoesNotRunTheParentStrategy() throws Exception {
+        Exchange exchange = process(new ChildProducer(endpoint(), null), 
"VOID", "World");
+
+        assertNull(exchange.getException());
+        assertEquals("World", exchange.getMessage().getBody());
+        assertEquals("child", exchange.getMessage().getHeader("ranBy"));
+    }
+
+    @Test
+    public void testUnknownValueOfTheHeaderFails() throws Exception {
+        Exchange exchange = process(new ChildProducer(endpoint(), null), 
"NOSUCH", "World");
+
+        IllegalArgumentException e = 
assertInstanceOf(IllegalArgumentException.class, exchange.getException());
+        assertEquals("Unsupported value NOSUCH of header MyAction", 
e.getMessage());
+        assertEquals("World", exchange.getMessage().getBody());
+    }
+
+    @Test
+    public void testUnknownDefaultValueFails() throws Exception {
+        Exchange exchange = process(new ChildProducer(endpoint(), "NOSUCH"), 
null, "World");
+
+        IllegalArgumentException e = 
assertInstanceOf(IllegalArgumentException.class, exchange.getException());
+        assertEquals("Unsupported value NOSUCH configured on the endpoint 
(header MyAction is not set)", e.getMessage());
+    }
+
+    @Test
+    public void testUnknownValueOfAStrategyFromOlderToolingDoesNothing() 
throws Exception {
+        // a strategy generated by older tooling returns null for an unknown 
value, as for a void method
+        Exchange exchange = process(new LegacyProducer(endpoint()), "NOSUCH", 
"World");
+
+        assertNull(exchange.getException());
+        assertEquals("World", exchange.getMessage().getBody());
+    }
+
+    private Endpoint endpoint() {
+        return context.getEndpoint("mock:result");
+    }
+
+    private Exchange process(Producer producer, String action, Object body) 
throws Exception {
+        ((HeaderSelectorProducer) producer).setCamelContext(context);
+        producer.start();
+        try {
+            Exchange exchange = new DefaultExchange(context);
+            if (action != null) {
+                exchange.getMessage().setHeader(HEADER, action);
+            }
+            exchange.getMessage().setBody(body);
+            producer.process(exchange);
+            return exchange;
+        } finally {
+            producer.stop();
+        }
+    }
+
+    public static class BaseProducer extends HeaderSelectorProducer {
+
+        public BaseProducer(Endpoint endpoint, String defaultValue) {
+            super(endpoint, HEADER, defaultValue);
+        }
+
+        public String onBase(Message message) {
+            return "Base " + message.getBody();
+        }
+
+        public void onBaseVoid(Message message) {
+            message.setHeader("ranBy", "base");
+        }
+    }
+
+    public static class ChildProducer extends BaseProducer {
+
+        public ChildProducer(Endpoint endpoint, String defaultValue) {
+            super(endpoint, defaultValue);
+        }
+
+        public String onHello(Message message) {
+            return "Hello " + message.getBody();
+        }
+
+        public void onVoid(Message message) {
+            message.setHeader("ranBy", "child");
+        }
+    }
+
+    public static class LegacyProducer extends HeaderSelectorProducer {
+
+        public LegacyProducer(Endpoint endpoint) {
+            super(endpoint, HEADER);
+        }
+
+        public String onHello(Message message) {
+            return "Hello " + message.getBody();
+        }
+    }
+
+    /**
+     * As generated for {@link BaseProducer}.
+     */
+    public static class BaseProducerStrategy implements InvokeOnHeaderStrategy 
{
+
+        @Override
+        public Object invoke(Object obj, String key, Exchange exchange, 
AsyncCallback callback) {
+            BaseProducer target = (BaseProducer) obj;
+            switch (key) {
+                case "BASE":
+                    return target.onBase(exchange.getMessage());
+                case "VOID":
+                    target.onBaseVoid(exchange.getMessage());
+                    return null;
+                default:
+                    return NO_METHOD;
+            }
+        }
+    }
+
+    /**
+     * As generated for {@link ChildProducer}.
+     */
+    public static class ChildProducerStrategy implements 
InvokeOnHeaderStrategy {
+
+        @Override
+        public Object invoke(Object obj, String key, Exchange exchange, 
AsyncCallback callback) {
+            ChildProducer target = (ChildProducer) obj;
+            switch (key) {
+                case "HELLO":
+                    return target.onHello(exchange.getMessage());
+                case "VOID":
+                    target.onVoid(exchange.getMessage());
+                    return null;
+                default:
+                    return NO_METHOD;
+            }
+        }
+    }
+
+    /**
+     * As generated by tooling older than {@link 
InvokeOnHeaderStrategy#NO_METHOD}.
+     */
+    public static class LegacyProducerStrategy implements 
InvokeOnHeaderStrategy {
+
+        @Override
+        public Object invoke(Object obj, String key, Exchange exchange, 
AsyncCallback callback) {
+            LegacyProducer target = (LegacyProducer) obj;
+            switch (key) {
+                case "HELLO":
+                    return target.onHello(exchange.getMessage());
+                default:
+                    return null;
+            }
+        }
+    }
+}
diff --git 
a/core/camel-support/src/main/java/org/apache/camel/support/component/AbstractApiConfiguration.java
 
b/core/camel-core/src/test/java/org/apache/camel/support/component/AbstractApiConfigurationTest.java
similarity index 57%
copy from 
core/camel-support/src/main/java/org/apache/camel/support/component/AbstractApiConfiguration.java
copy to 
core/camel-core/src/test/java/org/apache/camel/support/component/AbstractApiConfigurationTest.java
index 3a3728b453f3..5ecee79e5cfa 100644
--- 
a/core/camel-support/src/main/java/org/apache/camel/support/component/AbstractApiConfiguration.java
+++ 
b/core/camel-core/src/test/java/org/apache/camel/support/component/AbstractApiConfigurationTest.java
@@ -16,21 +16,15 @@
  */
 package org.apache.camel.support.component;
 
-import org.apache.camel.spi.UriParam;
+import org.junit.jupiter.api.Test;
 
-public class AbstractApiConfiguration {
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
-    @UriParam(label = "consumer", defaultValue = "true",
-              description = "When consumer return an array or collection this 
will generate one exchange per element, and their routes will be executed once 
for each exchange."
-                            + " Set this value to false to use a single 
exchange for the entire list or array.")
-    private boolean splitResult;
+public class AbstractApiConfigurationTest {
 
-    public boolean isSplitResult() {
-        return splitResult;
+    @Test
+    public void testSplitResultIsTrueByDefault() {
+        // as documented, and as the consumers of the API components that do 
not extend this configuration
+        assertTrue(new AbstractApiConfiguration().isSplitResult());
     }
-
-    public void setSplitResult(boolean splitResult) {
-        this.splitResult = splitResult;
-    }
-
 }
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/support/component/AbstractApiProducerTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/support/component/AbstractApiProducerTest.java
new file mode 100644
index 000000000000..5e9aab2b8761
--- /dev/null
+++ 
b/core/camel-core/src/test/java/org/apache/camel/support/component/AbstractApiProducerTest.java
@@ -0,0 +1,200 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.support.component;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Component;
+import org.apache.camel.Consumer;
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.Endpoint;
+import org.apache.camel.Processor;
+import org.apache.camel.Producer;
+import org.apache.camel.spi.PropertyConfigurer;
+import org.apache.camel.spi.PropertyConfigurerGetter;
+import org.apache.camel.support.DefaultComponent;
+import org.apache.camel.support.component.ApiMethodHelperTest.TestMethod;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * The producer of an API component converts the message body to the type of 
the parameter that inBody names.
+ */
+public class AbstractApiProducerTest extends ContextTestSupport {
+
+    private static final ApiMethodHelper<TestMethod> METHOD_HELPER
+            = new ApiMethodHelper<>(TestMethod.class, Map.of(), List.of());
+
+    @Test
+    public void testBodyIsConvertedToTheTypeOfTheInBodyParameter() throws 
Exception {
+        Endpoint endpoint = createEndpoint("greetMe", "name");
+
+        // the parameter is a String, and the endpoint configurer does not 
know it, as it is not an endpoint option
+        Object out = template.requestBody(endpoint, "World".getBytes());
+        assertEquals("Greetings World", out);
+    }
+
+    @Test
+    public void testBodyOfTheTypeOfTheInBodyParameter() throws Exception {
+        Endpoint endpoint = createEndpoint("greetMe", "name");
+
+        Object out = template.requestBody(endpoint, "World");
+        assertEquals("Greetings World", out);
+    }
+
+    @Test
+    public void testBodyIsConvertedToAPrimitiveParameter() throws Exception {
+        Endpoint endpoint = createEndpoint("greetTimes", "times", 
Map.of("name", "World"));
+
+        Object out = template.requestBody(endpoint, "2");
+        assertTrue(out instanceof String[], "Should be an array: " + out);
+        assertEquals(2, ((String[]) out).length);
+    }
+
+    private Endpoint createEndpoint(String methodName, String inBody) throws 
Exception {
+        return createEndpoint(methodName, inBody, Map.of());
+    }
+
+    private Endpoint createEndpoint(String methodName, String inBody, 
Map<String, Object> options) throws Exception {
+        TestApiComponent component = new TestApiComponent(context);
+        TestApiEndpoint endpoint = new TestApiEndpoint("test-api:" + 
methodName, component, methodName);
+        endpoint.setInBody(inBody);
+        endpoint.configureProperties(new HashMap<>(options));
+        context.addEndpoint(endpoint.getEndpointUri(), endpoint);
+        return endpoint;
+    }
+
+    enum TestApiName implements ApiName {
+        TEST;
+
+        @Override
+        public String getName() {
+            return "test";
+        }
+    }
+
+    public static class TestConfiguration {
+    }
+
+    @SuppressWarnings("unused")
+    public static class TestEndpointConfiguration extends TestConfiguration {
+        private String name;
+        private Integer times;
+
+        public String getName() {
+            return name;
+        }
+
+        public void setName(String name) {
+            this.name = name;
+        }
+
+        public Integer getTimes() {
+            return times;
+        }
+
+        public void setTimes(Integer times) {
+            this.times = times;
+        }
+    }
+
+    static class TestApiComponent extends DefaultComponent {
+
+        // as generated for an API component: it knows the options of the 
endpoint, not the parameters of the methods
+        private final PropertyConfigurer configurer = new 
TestEndpointConfigurer();
+
+        TestApiComponent(CamelContext context) {
+            super(context);
+        }
+
+        @Override
+        protected Endpoint createEndpoint(String uri, String remaining, 
Map<String, Object> parameters) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public PropertyConfigurer getEndpointPropertyConfigurer() {
+            return configurer;
+        }
+    }
+
+    static class TestEndpointConfigurer implements PropertyConfigurer, 
PropertyConfigurerGetter {
+
+        @Override
+        public boolean configure(CamelContext camelContext, Object target, 
String name, Object value, boolean ignoreCase) {
+            return false;
+        }
+
+        @Override
+        public Class<?> getOptionType(String name, boolean ignoreCase) {
+            return "inBody".equalsIgnoreCase(name) ? String.class : null;
+        }
+
+        @Override
+        public Object getOptionValue(Object target, String name, boolean 
ignoreCase) {
+            return null;
+        }
+    }
+
+    static class TestApiEndpoint extends AbstractApiEndpoint<TestApiName, 
TestConfiguration> {
+
+        private final ApiMethodPropertiesHelper<TestConfiguration> 
propertiesHelper;
+
+        TestApiEndpoint(String uri, Component component, String methodName) {
+            super(uri, component, TestApiName.TEST, methodName, METHOD_HELPER, 
new TestEndpointConfiguration());
+            this.propertiesHelper = new ApiMethodPropertiesHelper<>(
+                    component.getCamelContext(), TestConfiguration.class, 
"CamelTestApi.") {
+            };
+        }
+
+        @Override
+        protected ApiMethodPropertiesHelper<TestConfiguration> 
getPropertiesHelper() {
+            return propertiesHelper;
+        }
+
+        @Override
+        protected void afterConfigureProperties() {
+            // noop
+        }
+
+        @Override
+        public Object getApiProxy(ApiMethod method, Map<String, Object> args) {
+            return new TestProxy();
+        }
+
+        @Override
+        protected String getThreadProfileName() {
+            return "TestApiThreadProfile";
+        }
+
+        @Override
+        public Producer createProducer() {
+            return new AbstractApiProducer<>(this, propertiesHelper) {
+            };
+        }
+
+        @Override
+        public Consumer createConsumer(Processor processor) {
+            throw new UnsupportedOperationException();
+        }
+    }
+}
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/support/component/ApiMethodPropertiesHelperTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/support/component/ApiMethodPropertiesHelperTest.java
index 5cfac920d081..fe2dd91e5a71 100644
--- 
a/core/camel-core/src/test/java/org/apache/camel/support/component/ApiMethodPropertiesHelperTest.java
+++ 
b/core/camel-core/src/test/java/org/apache/camel/support/component/ApiMethodPropertiesHelperTest.java
@@ -66,6 +66,22 @@ public class ApiMethodPropertiesHelperTest {
         assertEquals(5, properties.size());
     }
 
+    @Test
+    public void testHeaderNamedAsThePrefixIsIgnored() {
+        final CamelContext camelContext = new DefaultCamelContext();
+        MockEndpoint mock = new MockEndpoint(null, new 
MockComponent(camelContext));
+
+        final HashMap<String, Object> properties = new HashMap<>();
+        final DefaultExchange exchange = DefaultExchange.newFromEndpoint(mock);
+        exchange.getIn().setHeader(PROPERTY_1, VALUE_1);
+        // a header named exactly as the camel case prefix, or the prefix, 
names no property
+        exchange.getIn().setHeader(TEST_PREFIX.substring(0, 
TEST_PREFIX.length() - 1), "camelCasePrefix");
+        exchange.getIn().setHeader(TEST_PREFIX, "prefix");
+        propertiesHelper.getExchangeProperties(exchange, properties);
+        assertEquals(1, properties.size());
+        assertEquals(VALUE_1, properties.get("property1"));
+    }
+
     @Test
     public void testGetEndpointProperties() {
         final HashMap<String, Object> properties = new HashMap<>();
diff --git 
a/core/camel-core/src/test/resources/META-INF/services/org/apache/camel/invoke-on-header/org.apache.camel.support.HeaderSelectorProducerTest$BaseProducer
 
b/core/camel-core/src/test/resources/META-INF/services/org/apache/camel/invoke-on-header/org.apache.camel.support.HeaderSelectorProducerTest$BaseProducer
new file mode 100644
index 000000000000..280a537fbfc9
--- /dev/null
+++ 
b/core/camel-core/src/test/resources/META-INF/services/org/apache/camel/invoke-on-header/org.apache.camel.support.HeaderSelectorProducerTest$BaseProducer
@@ -0,0 +1,18 @@
+#
+# 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.
+#
+
+class=org.apache.camel.support.HeaderSelectorProducerTest$BaseProducerStrategy
diff --git 
a/core/camel-core/src/test/resources/META-INF/services/org/apache/camel/invoke-on-header/org.apache.camel.support.HeaderSelectorProducerTest$ChildProducer
 
b/core/camel-core/src/test/resources/META-INF/services/org/apache/camel/invoke-on-header/org.apache.camel.support.HeaderSelectorProducerTest$ChildProducer
new file mode 100644
index 000000000000..b70e4f29dbdb
--- /dev/null
+++ 
b/core/camel-core/src/test/resources/META-INF/services/org/apache/camel/invoke-on-header/org.apache.camel.support.HeaderSelectorProducerTest$ChildProducer
@@ -0,0 +1,18 @@
+#
+# 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.
+#
+
+class=org.apache.camel.support.HeaderSelectorProducerTest$ChildProducerStrategy
diff --git 
a/core/camel-core/src/test/resources/META-INF/services/org/apache/camel/invoke-on-header/org.apache.camel.support.HeaderSelectorProducerTest$LegacyProducer
 
b/core/camel-core/src/test/resources/META-INF/services/org/apache/camel/invoke-on-header/org.apache.camel.support.HeaderSelectorProducerTest$LegacyProducer
new file mode 100644
index 000000000000..0114d35e751c
--- /dev/null
+++ 
b/core/camel-core/src/test/resources/META-INF/services/org/apache/camel/invoke-on-header/org.apache.camel.support.HeaderSelectorProducerTest$LegacyProducer
@@ -0,0 +1,18 @@
+#
+# 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.
+#
+
+class=org.apache.camel.support.HeaderSelectorProducerTest$LegacyProducerStrategy
diff --git 
a/core/camel-support/src/main/java/org/apache/camel/support/BodyFacts.java 
b/core/camel-support/src/main/java/org/apache/camel/support/BodyFacts.java
index 009434e7bc93..ef8584393b57 100644
--- a/core/camel-support/src/main/java/org/apache/camel/support/BodyFacts.java
+++ b/core/camel-support/src/main/java/org/apache/camel/support/BodyFacts.java
@@ -414,6 +414,9 @@ public final class BodyFacts {
             return BodyTypeRegistry.Kind.NULL;
         } else if (isPayload(body) || isValue(body)) {
             return BodyTypeRegistry.Kind.PAYLOAD;
+        } else if (body instanceof Map<?, ?>) {
+            // a Map that is also Iterable (such as a SimpleOrderedMap) is 
data, as describe calls it
+            return BodyTypeRegistry.Kind.DATA;
         } else if (body instanceof Collection<?> || body.getClass().isArray() 
|| isIterator(body)) {
             return BodyTypeRegistry.Kind.COLLECTION;
         }
diff --git 
a/core/camel-support/src/main/java/org/apache/camel/support/HeaderSelectorProducer.java
 
b/core/camel-support/src/main/java/org/apache/camel/support/HeaderSelectorProducer.java
index 5cb617324178..e5891611bde9 100644
--- 
a/core/camel-support/src/main/java/org/apache/camel/support/HeaderSelectorProducer.java
+++ 
b/core/camel-support/src/main/java/org/apache/camel/support/HeaderSelectorProducer.java
@@ -33,6 +33,8 @@ import org.slf4j.LoggerFactory;
 
 /**
  * A selector-based producer which uses a header value to determine which 
processor should be invoked.
+ * <p/>
+ * A value that has no method fails the exchange with an {@link 
IllegalArgumentException}.
  *
  * @see org.apache.camel.spi.InvokeOnHeader
  * @see InvokeOnHeaderStrategy
@@ -132,6 +134,7 @@ public abstract class HeaderSelectorProducer extends 
DefaultAsyncProducer implem
         try {
             String header = headerSupplier.get();
             String action = exchange.getIn().getHeader(header, String.class);
+            boolean fromHeader = action != null;
 
             if (action == null) {
                 action = defaultHeaderValueSupplier.get();
@@ -142,9 +145,18 @@ public abstract class HeaderSelectorProducer extends 
DefaultAsyncProducer implem
 
             LOG.debug("Invoking @InvokeOnHeader method: {}", action);
             Object answer = strategy.invoke(target, action, exchange, 
callback);
-            if (answer == null && parentStrategy != null) {
+            if (answer == InvokeOnHeaderStrategy.NO_METHOD && parentStrategy 
!= null) {
                 answer = parentStrategy.invoke(target, action, exchange, 
callback);
             }
+            if (answer == InvokeOnHeaderStrategy.NO_METHOD) {
+                // a strategy generated by older tooling returns null instead, 
and nothing happens
+                if (fromHeader) {
+                    throw new IllegalArgumentException("Unsupported value " + 
action + " of header " + header);
+                } else {
+                    throw new IllegalArgumentException(
+                            "Unsupported value " + action + " configured on 
the endpoint (header " + header + " is not set)");
+                }
+            }
             if (answer == callback) {
                 // okay it was an async-invoked so we should return false
                 sync = false;
diff --git 
a/core/camel-support/src/main/java/org/apache/camel/support/component/AbstractApiConfiguration.java
 
b/core/camel-support/src/main/java/org/apache/camel/support/component/AbstractApiConfiguration.java
index 3a3728b453f3..5bb9696e50ad 100644
--- 
a/core/camel-support/src/main/java/org/apache/camel/support/component/AbstractApiConfiguration.java
+++ 
b/core/camel-support/src/main/java/org/apache/camel/support/component/AbstractApiConfiguration.java
@@ -23,7 +23,7 @@ public class AbstractApiConfiguration {
     @UriParam(label = "consumer", defaultValue = "true",
               description = "When consumer return an array or collection this 
will generate one exchange per element, and their routes will be executed once 
for each exchange."
                             + " Set this value to false to use a single 
exchange for the entire list or array.")
-    private boolean splitResult;
+    private boolean splitResult = true;
 
     public boolean isSplitResult() {
         return splitResult;
diff --git 
a/core/camel-support/src/main/java/org/apache/camel/support/component/AbstractApiProducer.java
 
b/core/camel-support/src/main/java/org/apache/camel/support/component/AbstractApiProducer.java
index e59d0a9c5e0a..0780d860b2bb 100644
--- 
a/core/camel-support/src/main/java/org/apache/camel/support/component/AbstractApiProducer.java
+++ 
b/core/camel-support/src/main/java/org/apache/camel/support/component/AbstractApiProducer.java
@@ -16,6 +16,7 @@
  */
 package org.apache.camel.support.component;
 
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -174,18 +175,22 @@ public abstract class AbstractApiProducer<E extends 
Enum<E> & ApiName, T>
             Object value = exchange.getIn().getBody();
             if (value != null) {
                 try {
+                    Class<?> type = null;
                     // attempt to find out type via configurer so we avoid 
using reflection
                     PropertyConfigurer configurer = 
endpoint.getComponent().getEndpointPropertyConfigurer();
                     if (configurer instanceof PropertyConfigurerGetter getter) 
{
-                        Class<?> type = getter.getOptionType(inBodyProperty, 
true);
-                        if (type != null) {
-                            value = 
endpoint.getCamelContext().getTypeConverter().mandatoryConvertTo(type, 
exchange, value);
-                        }
-                    } else {
+                        type = getter.getOptionType(inBodyProperty, true);
+                    }
+                    if (type == null) {
+                        // the configurer knows the options of the endpoint, 
not the parameters of the API methods
+                        type = methodHelper.allArguments().get(inBodyProperty);
+                    }
+                    if (type == null && !(configurer instanceof 
PropertyConfigurerGetter)) {
                         // fallback to be reflection based
-                        value = 
endpoint.getCamelContext().getTypeConverter().mandatoryConvertTo(
-                                
endpoint.getConfiguration().getClass().getDeclaredField(inBodyProperty).getType(),
-                                exchange, value);
+                        type = 
endpoint.getConfiguration().getClass().getDeclaredField(inBodyProperty).getType();
+                    }
+                    if (type != null && !isArrayValueForArrayType(type, 
value)) {
+                        value = 
endpoint.getCamelContext().getTypeConverter().mandatoryConvertTo(type, 
exchange, value);
                     }
                 } catch (Exception e) {
                     exchange.setException(new RuntimeCamelException(
@@ -210,4 +215,9 @@ public abstract class AbstractApiProducer<E extends Enum<E> 
& ApiName, T>
 
         return true;
     }
+
+    // a collection or an array for an array parameter is converted when the 
method is invoked
+    private static boolean isArrayValueForArrayType(Class<?> type, Object 
value) {
+        return type.isArray() && (value instanceof Collection<?> || 
value.getClass().isArray());
+    }
 }
diff --git 
a/core/camel-support/src/main/java/org/apache/camel/support/component/ApiMethodPropertiesHelper.java
 
b/core/camel-support/src/main/java/org/apache/camel/support/component/ApiMethodPropertiesHelper.java
index a9dbfb71a7f6..f22a0896f7e2 100644
--- 
a/core/camel-support/src/main/java/org/apache/camel/support/component/ApiMethodPropertiesHelper.java
+++ 
b/core/camel-support/src/main/java/org/apache/camel/support/component/ApiMethodPropertiesHelper.java
@@ -88,6 +88,10 @@ public abstract class ApiMethodPropertiesHelper<C> {
         int nProperties = 0;
         for (Map.Entry<String, Object> entry : 
exchange.getIn().getHeaders().entrySet()) {
             final String key = entry.getKey();
+            if (key.length() < prefixLength || key.equals(propertyPrefix)) {
+                // a header named as the prefix, or as the camel case prefix, 
names no property
+                continue;
+            }
             if (key.startsWith(propertyPrefix)) {
                 properties.put(key.substring(prefixLength),
                         entry.getValue());
diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
index 55758c2545c6..6980f18648e5 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
@@ -2598,3 +2598,35 @@ With `crossOriginFilterOn=true`, the CORS responses now 
include `Access-Control-
 has an entry with a wildcard such as `https://*` or an entry `null`, the 
origin is still allowed, but without credentials. Previously credentials
 were allowed for every origin. A cross-origin client that sends cookies or 
other credentials must have its origin
 listed in `allowedOrigins`.
+
+=== Producers that pick their operation by a header - an unknown value fails
+
+A producer that picks its method by a header or an option (a 
`HeaderSelectorProducer`, used by camel-caffeine,
+camel-consul, camel-dynamic-router, camel-ehcache, camel-infinispan, 
camel-servicenow, camel-spring-redis and
+camel-web3j) now fails the exchange with an `IllegalArgumentException`, such as
+`Unsupported value NOSUCH of header CamelCaffeineAction`, when no method 
handles the value. Previously the exchange
+completed with no error and the body unchanged.
+
+The SPI `InvokeOnHeaderStrategy` has a new constant `NO_METHOD`, which the 
strategies generated by the Camel tooling
+now return for a key without a method. A strategy generated by older tooling 
returns null and keeps the old behaviour.
+The strategy of a parent class is now tried only for `NO_METHOD`, so a void 
method no longer runs the parent's
+strategy too; a third-party producer with a parent class that has its own 
strategy must have its strategies
+regenerated with the 4.24 tooling.
+
+=== API components - the inBody body is converted to the type of its parameter
+
+The API components (camel-as2, camel-box, camel-braintree, camel-dhis2, 
camel-fhir, camel-google-calendar,
+camel-google-drive, camel-google-mail, camel-google-sheets, camel-olingo2, 
camel-olingo4, camel-twilio and
+camel-zendesk) now convert the message body to the type of the method 
parameter that `inBody` names, for example a
+`byte[]`, a stream or a file to a `String`. Previously the body was passed as 
it was, and a body of another type failed
+with `argument type mismatch`; a body that cannot be converted now fails 
before the call, with an error that names the
+property.
+
+A header named exactly as the prefix of an API component, such as `CamelFhir` 
or `CamelFhir.`, is now ignored.
+Previously it failed every call with a `StringIndexOutOfBoundsException`.
+
+=== camel-google-sheets - splitResult is true by default
+
+The `splitResult` option of the google-sheets consumer now defaults to `true`, 
as documented and as the other API
+components do: an array or collection result gives one exchange per element. 
Set `splitResult=false` for one exchange
+with the whole result.
diff --git 
a/tooling/maven/camel-package-maven-plugin/src/main/resources/velocity/invoke-on-header.vm
 
b/tooling/maven/camel-package-maven-plugin/src/main/resources/velocity/invoke-on-header.vm
index d23e0b52f42c..758c244084e4 100644
--- 
a/tooling/maven/camel-package-maven-plugin/src/main/resources/velocity/invoke-on-header.vm
+++ 
b/tooling/maven/camel-package-maven-plugin/src/main/resources/velocity/invoke-on-header.vm
@@ -61,7 +61,7 @@ public class ${cn} implements InvokeOnHeaderStrategy {
         case "${option.key}": return target.${option.methodName}(${args});
         #end
     #end
-        default: return null;
+        default: return InvokeOnHeaderStrategy.NO_METHOD;
         }
 #end
     }

Reply via email to