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

orpiske pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new 1cc74b3e4d38 (test): add quality test coverage for camel-rest component
1cc74b3e4d38 is described below

commit 1cc74b3e4d38be052ea5c296b555e3f0d8e440af
Author: Otavio Rodolfo Piske <[email protected]>
AuthorDate: Fri Jan 30 09:02:47 2026 +0000

    (test): add quality test coverage for camel-rest component
    
    Add unit tests for camel-rest component focusing on meaningful behavior 
tests:
    - RestComponent URI parsing and endpoint creation
    - RestEndpoint behavior and error handling
    - RestProducer query parameter resolution and exchange preparation
    - RestApiEndpoint creation and factory interactions
    - DefaultRestRegistry service management
    - RestProducerBindingProcessor and callback behavior
    
    Tests cover:
    - Error conditions and exception handling
    - URI template placeholder resolution
    - Query parameter substitution with optional placeholders
    - Host scheme handling and normalization
    - Component property propagation
    - Consumer/producer creation with factories
---
 components/camel-rest/pom.xml                      |  28 ++
 .../component/rest/DefaultRestRegistryTest.java    | 198 ++++++++++
 .../camel/component/rest/RestApiEndpointTest.java  |  99 +++++
 .../camel/component/rest/RestComponentTest.java    | 200 ++++++++++
 .../rest/RestEndpointProducerConsumerTest.java     | 394 ++++++++++++++++++++
 .../camel/component/rest/RestEndpointTest.java     | 135 +++++++
 .../component/rest/RestProducerAdvancedTest.java   | 334 +++++++++++++++++
 .../rest/RestProducerBindingCallbackTest.java      | 402 +++++++++++++++++++++
 .../rest/RestProducerBindingProcessorTest.java     | 311 ++++++++++++++++
 .../camel/component/rest/RestProducerTest.java     | 354 ++++++++++++++++++
 .../component/rest/RestRegistryStatefulTest.java   | 148 ++++++++
 11 files changed, 2603 insertions(+)

diff --git a/components/camel-rest/pom.xml b/components/camel-rest/pom.xml
index e78a91271f69..06c0ec97fffd 100644
--- a/components/camel-rest/pom.xml
+++ b/components/camel-rest/pom.xml
@@ -39,5 +39,33 @@
             <artifactId>camel-support</artifactId>
         </dependency>
 
+        <!-- testing -->
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-core-engine</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-core-languages</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-junit-jupiter</artifactId>
+            <version>${mockito-version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.assertj</groupId>
+            <artifactId>assertj-core</artifactId>
+            <scope>test</scope>
+        </dependency>
+
     </dependencies>
 </project>
diff --git 
a/components/camel-rest/src/test/java/org/apache/camel/component/rest/DefaultRestRegistryTest.java
 
b/components/camel-rest/src/test/java/org/apache/camel/component/rest/DefaultRestRegistryTest.java
new file mode 100644
index 000000000000..136aab89d2f4
--- /dev/null
+++ 
b/components/camel-rest/src/test/java/org/apache/camel/component/rest/DefaultRestRegistryTest.java
@@ -0,0 +1,198 @@
+/*
+ * 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.rest;
+
+import java.util.List;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Consumer;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.spi.RestRegistry;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@ExtendWith(MockitoExtension.class)
+class DefaultRestRegistryTest {
+
+    private DefaultRestRegistry registry;
+    private CamelContext camelContext;
+
+    @Mock
+    private Consumer consumer1;
+
+    @Mock
+    private Consumer consumer2;
+
+    @BeforeEach
+    void setUp() throws Exception {
+        camelContext = new DefaultCamelContext();
+        registry = new DefaultRestRegistry();
+        registry.setCamelContext(camelContext);
+        registry.start();
+    }
+
+    @AfterEach
+    void tearDown() throws Exception {
+        registry.stop();
+        camelContext.stop();
+    }
+
+    @Test
+    void testAddRestService() {
+        registry.addRestService(consumer1, false, 
"http://localhost:8080/api/users";,
+                "http://localhost:8080";, "/api", "/users", "GET",
+                "application/json", "application/json", "User", "User",
+                "route1", "Get all users");
+
+        assertThat(registry.size()).isEqualTo(1);
+    }
+
+    @Test
+    void testAddMultipleRestServices() {
+        registry.addRestService(consumer1, false, 
"http://localhost:8080/api/users";,
+                "http://localhost:8080";, "/api", "/users", "GET",
+                "application/json", "application/json", null, null,
+                "route1", "Get all users");
+
+        registry.addRestService(consumer2, false, 
"http://localhost:8080/api/orders";,
+                "http://localhost:8080";, "/api", "/orders", "POST",
+                "application/json", "application/json", "Order", "Order",
+                "route2", "Create order");
+
+        assertThat(registry.size()).isEqualTo(2);
+    }
+
+    @Test
+    void testAddMultipleServicesToSameConsumer() {
+        registry.addRestService(consumer1, false, 
"http://localhost:8080/api/users";,
+                "http://localhost:8080";, "/api", "/users", "GET",
+                null, null, null, null, "route1", "Get users");
+
+        registry.addRestService(consumer1, false, 
"http://localhost:8080/api/users/{id}";,
+                "http://localhost:8080";, "/api", "/users/{id}", "GET",
+                null, null, null, null, "route2", "Get user by id");
+
+        assertThat(registry.size()).isEqualTo(2);
+    }
+
+    @Test
+    void testRemoveRestService() {
+        registry.addRestService(consumer1, false, 
"http://localhost:8080/api/users";,
+                "http://localhost:8080";, "/api", "/users", "GET",
+                null, null, null, null, "route1", "Get users");
+
+        assertThat(registry.size()).isEqualTo(1);
+
+        registry.removeRestService(consumer1);
+
+        assertThat(registry.size()).isEqualTo(0);
+    }
+
+    @Test
+    void testRemoveNonExistentService() {
+        registry.addRestService(consumer1, false, 
"http://localhost:8080/api/users";,
+                "http://localhost:8080";, "/api", "/users", "GET",
+                null, null, null, null, "route1", "Get users");
+
+        registry.removeRestService(consumer2);
+
+        assertThat(registry.size()).isEqualTo(1);
+    }
+
+    @Test
+    void testListAllRestServices() {
+        registry.addRestService(consumer1, false, 
"http://localhost:8080/api/users";,
+                "http://localhost:8080";, "/api", "/users", "GET",
+                "application/json", "application/json", "User", "UserResponse",
+                "route1", "Get all users");
+
+        List<RestRegistry.RestService> services = 
registry.listAllRestServices();
+
+        assertThat(services).hasSize(1);
+
+        RestRegistry.RestService service = services.get(0);
+        
assertThat(service.getUrl()).isEqualTo("http://localhost:8080/api/users";);
+        assertThat(service.getBaseUrl()).isEqualTo("http://localhost:8080";);
+        assertThat(service.getBasePath()).isEqualTo("/api");
+        assertThat(service.getUriTemplate()).isEqualTo("/users");
+        assertThat(service.getMethod()).isEqualTo("GET");
+        assertThat(service.getConsumes()).isEqualTo("application/json");
+        assertThat(service.getProduces()).isEqualTo("application/json");
+        assertThat(service.getInType()).isEqualTo("User");
+        assertThat(service.getOutType()).isEqualTo("UserResponse");
+        assertThat(service.getDescription()).isEqualTo("Get all users");
+        assertThat(service.getConsumer()).isSameAs(consumer1);
+        assertThat(service.isContractFirst()).isFalse();
+    }
+
+    @Test
+    void testContractFirstService() {
+        registry.addRestService(consumer1, true, 
"http://localhost:8080/api/users";,
+                "http://localhost:8080";, "/api", "/users", "GET",
+                null, null, null, null, "route1", "Get users");
+
+        List<RestRegistry.RestService> services = 
registry.listAllRestServices();
+        assertThat(services.get(0).isContractFirst()).isTrue();
+    }
+
+    @Test
+    void testSizeWithEmptyRegistry() {
+        assertThat(registry.size()).isEqualTo(0);
+    }
+
+    @Test
+    void testListAllRestServicesEmpty() {
+        List<RestRegistry.RestService> services = 
registry.listAllRestServices();
+        assertThat(services).isEmpty();
+    }
+
+    @Test
+    void testServiceState() {
+        registry.addRestService(consumer1, false, 
"http://localhost:8080/api/users";,
+                "http://localhost:8080";, "/api", "/users", "GET",
+                null, null, null, null, "route1", "Get users");
+
+        List<RestRegistry.RestService> services = 
registry.listAllRestServices();
+        // Non-stateful consumer returns Stopped state
+        assertThat(services.get(0).getState()).isEqualTo("Stopped");
+    }
+
+    @Test
+    void testApiDocAsJsonWithNoEndpoints() {
+        String apiDoc = registry.apiDocAsJson();
+        assertThat(apiDoc).isNull();
+    }
+
+    @Test
+    void testStopClearsRegistry() throws Exception {
+        registry.addRestService(consumer1, false, 
"http://localhost:8080/api/users";,
+                "http://localhost:8080";, "/api", "/users", "GET",
+                null, null, null, null, "route1", "Get users");
+
+        assertThat(registry.size()).isEqualTo(1);
+
+        registry.stop();
+
+        assertThat(registry.size()).isEqualTo(0);
+    }
+}
diff --git 
a/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestApiEndpointTest.java
 
b/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestApiEndpointTest.java
new file mode 100644
index 000000000000..adf28eec9db2
--- /dev/null
+++ 
b/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestApiEndpointTest.java
@@ -0,0 +1,99 @@
+/*
+ * 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.rest;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.ExchangePattern;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/**
+ * Tests for RestApiEndpoint focusing on behavior and error handling.
+ */
+class RestApiEndpointTest {
+
+    private CamelContext camelContext;
+    private RestApiComponent component;
+
+    @BeforeEach
+    void setUp() throws Exception {
+        camelContext = new DefaultCamelContext();
+        component = new RestApiComponent();
+        component.setCamelContext(camelContext);
+        camelContext.addComponent("rest-api", component);
+        camelContext.start();
+    }
+
+    @AfterEach
+    void tearDown() throws Exception {
+        camelContext.stop();
+    }
+
+    @Test
+    void testDefaultExchangePattern() throws Exception {
+        RestApiEndpoint endpoint = (RestApiEndpoint) 
camelContext.getEndpoint("rest-api:api-doc");
+        
assertThat(endpoint.getExchangePattern()).isEqualTo(ExchangePattern.InOut);
+    }
+
+    @Test
+    void testIsNotRemote() throws Exception {
+        RestApiEndpoint endpoint = (RestApiEndpoint) 
camelContext.getEndpoint("rest-api:api-doc");
+        assertThat(endpoint.isRemote()).isFalse();
+    }
+
+    @Test
+    void testIsLenientProperties() throws Exception {
+        RestApiEndpoint endpoint = (RestApiEndpoint) 
camelContext.getEndpoint("rest-api:api-doc");
+        assertThat(endpoint.isLenientProperties()).isTrue();
+    }
+
+    @Test
+    void testCreateProducerWithoutFactory() throws Exception {
+        RestApiEndpoint endpoint = (RestApiEndpoint) 
camelContext.getEndpoint("rest-api:api-doc");
+
+        assertThatThrownBy(endpoint::createProducer)
+                .isInstanceOf(IllegalStateException.class)
+                .hasMessageContaining("Cannot find RestApiProcessorFactory");
+    }
+
+    @Test
+    void testCreateConsumerWithoutFactory() throws Exception {
+        RestApiEndpoint endpoint = (RestApiEndpoint) 
camelContext.getEndpoint("rest-api:api-doc");
+
+        assertThatThrownBy(() -> endpoint.createConsumer(exchange -> {
+        }))
+                .isInstanceOf(IllegalStateException.class)
+                .hasMessageContaining("Cannot find RestApiConsumerFactory");
+    }
+
+    @Test
+    void testPathWithLeadingSlash() throws Exception {
+        RestApiEndpoint endpoint = (RestApiEndpoint) 
camelContext.getEndpoint("rest-api:/api-doc");
+        assertThat(endpoint.getPath()).isEqualTo("/api-doc");
+    }
+
+    @Test
+    void testPathWithNestedPath() throws Exception {
+        RestApiEndpoint endpoint = (RestApiEndpoint) 
camelContext.getEndpoint("rest-api:api/v2/doc");
+        assertThat(endpoint.getPath()).isEqualTo("api/v2/doc");
+    }
+}
diff --git 
a/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestComponentTest.java
 
b/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestComponentTest.java
new file mode 100644
index 000000000000..dbe30efb951b
--- /dev/null
+++ 
b/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestComponentTest.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.component.rest;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Endpoint;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/**
+ * Tests for RestComponent focusing on URI parsing and endpoint creation 
behavior.
+ */
+class RestComponentTest {
+
+    private CamelContext camelContext;
+    private RestComponent component;
+
+    @BeforeEach
+    void setUp() throws Exception {
+        camelContext = new DefaultCamelContext();
+        component = new RestComponent();
+        component.setCamelContext(camelContext);
+        camelContext.addComponent("rest", component);
+        camelContext.start();
+    }
+
+    @AfterEach
+    void tearDown() throws Exception {
+        camelContext.stop();
+    }
+
+    // ==================== URI Parsing Tests ====================
+
+    @Test
+    void testCreateEndpointParsesMethodAndPath() throws Exception {
+        Endpoint endpoint = 
camelContext.getEndpoint("rest:get:users?host=localhost:8080");
+
+        assertThat(endpoint).isInstanceOf(RestEndpoint.class);
+
+        RestEndpoint restEndpoint = (RestEndpoint) endpoint;
+        assertThat(restEndpoint.getMethod()).isEqualTo("get");
+        assertThat(restEndpoint.getPath()).isEqualTo("users");
+        assertThat(restEndpoint.getUriTemplate()).isNull();
+    }
+
+    @Test
+    void testCreateEndpointParsesMethodPathAndUriTemplate() throws Exception {
+        Endpoint endpoint = 
camelContext.getEndpoint("rest:get:users:{id}?host=localhost:8080");
+
+        RestEndpoint restEndpoint = (RestEndpoint) endpoint;
+        assertThat(restEndpoint.getMethod()).isEqualTo("get");
+        assertThat(restEndpoint.getPath()).isEqualTo("users");
+        assertThat(restEndpoint.getUriTemplate()).isEqualTo("{id}");
+    }
+
+    @Test
+    void testCreateEndpointSupportsAllHttpMethods() throws Exception {
+        String[] methods = { "get", "post", "put", "delete", "patch", "head", 
"options" };
+
+        for (String method : methods) {
+            Endpoint endpoint = camelContext.getEndpoint("rest:" + method + 
":test?host=localhost");
+            RestEndpoint restEndpoint = (RestEndpoint) endpoint;
+            assertThat(restEndpoint.getMethod()).isEqualTo(method);
+        }
+    }
+
+    @Test
+    void testCreateEndpointThrowsExceptionForInvalidSyntax() {
+        assertThatThrownBy(() -> camelContext.getEndpoint("rest:get"))
+                .hasMessageContaining("Invalid syntax");
+    }
+
+    @Test
+    void testCreateEndpointStripsTrailingSlashFromPath() throws Exception {
+        Endpoint endpoint = 
camelContext.getEndpoint("rest:get:users/?host=localhost");
+
+        RestEndpoint restEndpoint = (RestEndpoint) endpoint;
+        assertThat(restEndpoint.getPath()).isEqualTo("users");
+    }
+
+    // ==================== Host Handling Tests ====================
+
+    @Test
+    void testCreateEndpointAddsHttpPrefixWhenMissing() throws Exception {
+        Endpoint endpoint = 
camelContext.getEndpoint("rest:get:users?host=localhost:8080");
+
+        RestEndpoint restEndpoint = (RestEndpoint) endpoint;
+        assertThat(restEndpoint.getHost()).isEqualTo("http://localhost:8080";);
+    }
+
+    @Test
+    void testCreateEndpointPreservesHttpScheme() throws Exception {
+        Endpoint endpoint = 
camelContext.getEndpoint("rest:get:users?host=http://localhost:8080";);
+
+        RestEndpoint restEndpoint = (RestEndpoint) endpoint;
+        assertThat(restEndpoint.getHost()).isEqualTo("http://localhost:8080";);
+    }
+
+    @Test
+    void testCreateEndpointPreservesHttpsScheme() throws Exception {
+        Endpoint endpoint = 
camelContext.getEndpoint("rest:get:users?host=https://localhost:8443";);
+
+        RestEndpoint restEndpoint = (RestEndpoint) endpoint;
+        assertThat(restEndpoint.getHost()).isEqualTo("https://localhost:8443";);
+    }
+
+    // ==================== Component Property Propagation Tests 
====================
+
+    @Test
+    void testComponentConsumerNamePropagatestoEndpoint() throws Exception {
+        component.setConsumerComponentName("servlet");
+        Endpoint endpoint = 
camelContext.getEndpoint("rest:get:users?host=localhost");
+
+        RestEndpoint restEndpoint = (RestEndpoint) endpoint;
+        
assertThat(restEndpoint.getConsumerComponentName()).isEqualTo("servlet");
+    }
+
+    @Test
+    void testComponentProducerNamePropagatestoEndpoint() throws Exception {
+        component.setProducerComponentName("undertow");
+        Endpoint endpoint = 
camelContext.getEndpoint("rest:get:users?host=localhost");
+
+        RestEndpoint restEndpoint = (RestEndpoint) endpoint;
+        
assertThat(restEndpoint.getProducerComponentName()).isEqualTo("undertow");
+    }
+
+    @Test
+    void testComponentApiDocPropagatestoEndpoint() throws Exception {
+        component.setApiDoc("swagger.json");
+        Endpoint endpoint = 
camelContext.getEndpoint("rest:get:users?host=localhost");
+
+        RestEndpoint restEndpoint = (RestEndpoint) endpoint;
+        assertThat(restEndpoint.getApiDoc()).isEqualTo("swagger.json");
+    }
+
+    // ==================== Endpoint Options Tests ====================
+
+    @Test
+    void testCreateEndpointWithConsumesOption() throws Exception {
+        Endpoint endpoint = 
camelContext.getEndpoint("rest:post:users?host=localhost&consumes=application/json");
+
+        RestEndpoint restEndpoint = (RestEndpoint) endpoint;
+        assertThat(restEndpoint.getConsumes()).isEqualTo("application/json");
+    }
+
+    @Test
+    void testCreateEndpointWithProducesOption() throws Exception {
+        Endpoint endpoint = 
camelContext.getEndpoint("rest:get:users?host=localhost&produces=application/xml");
+
+        RestEndpoint restEndpoint = (RestEndpoint) endpoint;
+        assertThat(restEndpoint.getProduces()).isEqualTo("application/xml");
+    }
+
+    @Test
+    void testCreateEndpointWithBindingModeOption() throws Exception {
+        Endpoint endpoint = 
camelContext.getEndpoint("rest:get:users?host=localhost&bindingMode=json");
+
+        RestEndpoint restEndpoint = (RestEndpoint) endpoint;
+        assertThat(restEndpoint.getBindingMode()).isNotNull();
+    }
+
+    @Test
+    void testCreateEndpointWithInTypeAndOutType() throws Exception {
+        Endpoint endpoint = camelContext.getEndpoint(
+                
"rest:post:users?host=localhost&inType=com.example.User&outType=com.example.UserResponse");
+
+        RestEndpoint restEndpoint = (RestEndpoint) endpoint;
+        assertThat(restEndpoint.getInType()).isEqualTo("com.example.User");
+        
assertThat(restEndpoint.getOutType()).isEqualTo("com.example.UserResponse");
+    }
+
+    @Test
+    void testCreateEndpointWithComponentNames() throws Exception {
+        Endpoint endpoint = camelContext.getEndpoint(
+                
"rest:get:users?host=localhost&consumerComponentName=jetty&producerComponentName=http");
+
+        RestEndpoint restEndpoint = (RestEndpoint) endpoint;
+        assertThat(restEndpoint.getConsumerComponentName()).isEqualTo("jetty");
+        assertThat(restEndpoint.getProducerComponentName()).isEqualTo("http");
+    }
+}
diff --git 
a/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestEndpointProducerConsumerTest.java
 
b/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestEndpointProducerConsumerTest.java
new file mode 100644
index 000000000000..eeee8c86fa5c
--- /dev/null
+++ 
b/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestEndpointProducerConsumerTest.java
@@ -0,0 +1,394 @@
+/*
+ * 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.rest;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Consumer;
+import org.apache.camel.Producer;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.spi.RestConfiguration;
+import org.apache.camel.spi.RestConsumerFactory;
+import org.apache.camel.spi.RestProducerFactory;
+import org.apache.camel.support.SimpleRegistry;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+class RestEndpointProducerConsumerTest {
+
+    private CamelContext camelContext;
+    private SimpleRegistry registry;
+    private RestComponent component;
+
+    @Mock
+    private RestProducerFactory mockProducerFactory;
+
+    @Mock
+    private RestConsumerFactory mockConsumerFactory;
+
+    @Mock
+    private Producer mockProducer;
+
+    @Mock
+    private Consumer mockConsumer;
+
+    @BeforeEach
+    void setUp() throws Exception {
+        registry = new SimpleRegistry();
+        camelContext = new DefaultCamelContext(registry);
+        component = new RestComponent();
+        component.setCamelContext(camelContext);
+        camelContext.addComponent("rest", component);
+        camelContext.start();
+    }
+
+    @AfterEach
+    void tearDown() throws Exception {
+        camelContext.stop();
+    }
+
+    @Test
+    void testCreateProducerWithRegisteredFactory() throws Exception {
+        registry.bind("myProducerFactory", mockProducerFactory);
+
+        when(mockProducerFactory.createProducer(
+                any(CamelContext.class), eq("http://localhost:8080";), 
eq("get"),
+                eq("users"), any(), any(), any(), any(),
+                any(RestConfiguration.class), any(Map.class)))
+                .thenReturn(mockProducer);
+
+        RestEndpoint endpoint = (RestEndpoint) camelContext.getEndpoint(
+                
"rest:get:users?host=localhost:8080&producerComponentName=myProducerFactory");
+
+        Producer producer = endpoint.createProducer();
+
+        assertThat(producer).isNotNull();
+        assertThat(producer).isInstanceOf(RestProducer.class);
+    }
+
+    @Test
+    void testCreateProducerWithNonRestProducerFactoryComponent() throws 
Exception {
+        registry.bind("myComponent", new Object());
+
+        RestEndpoint endpoint = (RestEndpoint) camelContext.getEndpoint(
+                
"rest:get:users?host=localhost:8080&producerComponentName=myComponent");
+
+        assertThatThrownBy(endpoint::createProducer)
+                .hasMessageContaining("RestProducerFactory");
+    }
+
+    @Test
+    void testCreateConsumerWithRegisteredFactory() throws Exception {
+        registry.bind("myConsumerFactory", mockConsumerFactory);
+
+        when(mockConsumerFactory.createConsumer(
+                any(CamelContext.class), any(), any(),
+                any(), any(), any(), any(),
+                any(RestConfiguration.class), any(Map.class)))
+                .thenReturn(mockConsumer);
+
+        RestEndpoint endpoint = (RestEndpoint) camelContext.getEndpoint(
+                
"rest:get:users?host=localhost:8080&consumerComponentName=myConsumerFactory");
+
+        Consumer consumer = endpoint.createConsumer(exchange -> {
+        });
+
+        assertThat(consumer).isNotNull();
+    }
+
+    @Test
+    void testCreateConsumerWithNonRestConsumerFactoryComponent() throws 
Exception {
+        registry.bind("myComponent", new Object());
+
+        RestEndpoint endpoint = (RestEndpoint) camelContext.getEndpoint(
+                
"rest:get:users?host=localhost:8080&consumerComponentName=myComponent");
+
+        assertThatThrownBy(() -> endpoint.createConsumer(exchange -> {
+        }))
+                .hasMessageContaining("RestConsumerFactory");
+    }
+
+    @Test
+    void testProducerWithConsumerFallback() throws Exception {
+        // Register a factory that implements both producer and consumer
+        RestProducerFactory dualFactory = mock(RestProducerFactory.class);
+        registry.bind("dualFactory", dualFactory);
+
+        when(dualFactory.createProducer(
+                any(CamelContext.class), any(), any(),
+                any(), any(), any(), any(), any(),
+                any(RestConfiguration.class), any(Map.class)))
+                .thenReturn(mockProducer);
+
+        RestEndpoint endpoint = (RestEndpoint) camelContext.getEndpoint(
+                
"rest:get:users?host=localhost:8080&consumerComponentName=dualFactory");
+
+        // With no explicit producer component, it should fall back to 
consumer component
+        Producer producer = endpoint.createProducer();
+
+        assertThat(producer).isNotNull();
+    }
+
+    @Test
+    void testCreateProducerWithEmptyHost() throws Exception {
+        RestEndpoint endpoint = new RestEndpoint("rest:get:users", component);
+        endpoint.setMethod("get");
+        endpoint.setPath("users");
+        endpoint.setHost("");
+        endpoint.setParameters(new HashMap<>());
+
+        assertThatThrownBy(endpoint::createProducer)
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("Hostname must be configured");
+    }
+
+    @Test
+    void testCreateConsumerWithRestConfiguration() throws Exception {
+        RestConfiguration restConfig = new RestConfiguration();
+        restConfig.setHost("localhost");
+        restConfig.setPort(9090);
+        restConfig.setScheme("https");
+        restConfig.setContextPath("/api");
+        camelContext.setRestConfiguration(restConfig);
+
+        registry.bind("myConsumerFactory", mockConsumerFactory);
+
+        when(mockConsumerFactory.createConsumer(
+                any(CamelContext.class), any(), any(),
+                any(), any(), any(), any(),
+                any(RestConfiguration.class), any(Map.class)))
+                .thenReturn(mockConsumer);
+
+        RestEndpoint endpoint = (RestEndpoint) camelContext.getEndpoint(
+                
"rest:get:users?host=localhost&consumerComponentName=myConsumerFactory");
+
+        Consumer consumer = endpoint.createConsumer(exchange -> {
+        });
+
+        assertThat(consumer).isNotNull();
+    }
+
+    @Test
+    void testCreateConsumerWithHostNameResolver() throws Exception {
+        RestConfiguration restConfig = new RestConfiguration();
+        
restConfig.setHostNameResolver(RestConfiguration.RestHostNameResolver.localIp);
+        camelContext.setRestConfiguration(restConfig);
+
+        registry.bind("myConsumerFactory", mockConsumerFactory);
+
+        when(mockConsumerFactory.createConsumer(
+                any(CamelContext.class), any(), any(),
+                any(), any(), any(), any(),
+                any(RestConfiguration.class), any(Map.class)))
+                .thenReturn(mockConsumer);
+
+        RestEndpoint endpoint = (RestEndpoint) camelContext.getEndpoint(
+                
"rest:get:users?host=localhost&consumerComponentName=myConsumerFactory");
+
+        Consumer consumer = endpoint.createConsumer(exchange -> {
+        });
+
+        assertThat(consumer).isNotNull();
+    }
+
+    @Test
+    void testCreateConsumerWithAllLocalIpResolver() throws Exception {
+        RestConfiguration restConfig = new RestConfiguration();
+        
restConfig.setHostNameResolver(RestConfiguration.RestHostNameResolver.allLocalIp);
+        camelContext.setRestConfiguration(restConfig);
+
+        registry.bind("myConsumerFactory", mockConsumerFactory);
+
+        when(mockConsumerFactory.createConsumer(
+                any(CamelContext.class), any(), any(),
+                any(), any(), any(), any(),
+                any(RestConfiguration.class), any(Map.class)))
+                .thenReturn(mockConsumer);
+
+        RestEndpoint endpoint = (RestEndpoint) camelContext.getEndpoint(
+                
"rest:get:users?host=localhost&consumerComponentName=myConsumerFactory");
+
+        Consumer consumer = endpoint.createConsumer(exchange -> {
+        });
+
+        assertThat(consumer).isNotNull();
+    }
+
+    @Test
+    void testCreateConsumerWithLocalHostNameResolver() throws Exception {
+        RestConfiguration restConfig = new RestConfiguration();
+        
restConfig.setHostNameResolver(RestConfiguration.RestHostNameResolver.localHostName);
+        camelContext.setRestConfiguration(restConfig);
+
+        registry.bind("myConsumerFactory", mockConsumerFactory);
+
+        when(mockConsumerFactory.createConsumer(
+                any(CamelContext.class), any(), any(),
+                any(), any(), any(), any(),
+                any(RestConfiguration.class), any(Map.class)))
+                .thenReturn(mockConsumer);
+
+        RestEndpoint endpoint = (RestEndpoint) camelContext.getEndpoint(
+                
"rest:get:users?host=localhost&consumerComponentName=myConsumerFactory");
+
+        Consumer consumer = endpoint.createConsumer(exchange -> {
+        });
+
+        assertThat(consumer).isNotNull();
+    }
+
+    @Test
+    void testCreateConsumerWithUriTemplateStartingWithSlash() throws Exception 
{
+        registry.bind("myConsumerFactory", mockConsumerFactory);
+
+        when(mockConsumerFactory.createConsumer(
+                any(CamelContext.class), any(), any(),
+                any(), any(), any(), any(),
+                any(RestConfiguration.class), any(Map.class)))
+                .thenReturn(mockConsumer);
+
+        RestEndpoint endpoint = (RestEndpoint) camelContext.getEndpoint(
+                
"rest:get:users:/{id}?host=localhost&consumerComponentName=myConsumerFactory");
+
+        Consumer consumer = endpoint.createConsumer(exchange -> {
+        });
+
+        assertThat(consumer).isNotNull();
+    }
+
+    @Test
+    void testCreateConsumerWithPathNotStartingWithSlash() throws Exception {
+        registry.bind("myConsumerFactory", mockConsumerFactory);
+
+        when(mockConsumerFactory.createConsumer(
+                any(CamelContext.class), any(), any(),
+                any(), any(), any(), any(),
+                any(RestConfiguration.class), any(Map.class)))
+                .thenReturn(mockConsumer);
+
+        RestEndpoint endpoint = (RestEndpoint) camelContext.getEndpoint(
+                
"rest:get:users?host=localhost&consumerComponentName=myConsumerFactory");
+
+        Consumer consumer = endpoint.createConsumer(exchange -> {
+        });
+
+        assertThat(consumer).isNotNull();
+    }
+
+    @Test
+    void testCreateConsumerWithContextPathStartingWithSlash() throws Exception 
{
+        RestConfiguration restConfig = new RestConfiguration();
+        restConfig.setContextPath("/api/v1");
+        camelContext.setRestConfiguration(restConfig);
+
+        registry.bind("myConsumerFactory", mockConsumerFactory);
+
+        when(mockConsumerFactory.createConsumer(
+                any(CamelContext.class), any(), any(),
+                any(), any(), any(), any(),
+                any(RestConfiguration.class), any(Map.class)))
+                .thenReturn(mockConsumer);
+
+        RestEndpoint endpoint = (RestEndpoint) camelContext.getEndpoint(
+                
"rest:get:users?host=localhost&consumerComponentName=myConsumerFactory");
+
+        Consumer consumer = endpoint.createConsumer(exchange -> {
+        });
+
+        assertThat(consumer).isNotNull();
+    }
+
+    @Test
+    void testCreateConsumerWithContextPathNotStartingWithSlash() throws 
Exception {
+        RestConfiguration restConfig = new RestConfiguration();
+        restConfig.setContextPath("api/v1");
+        camelContext.setRestConfiguration(restConfig);
+
+        registry.bind("myConsumerFactory", mockConsumerFactory);
+
+        when(mockConsumerFactory.createConsumer(
+                any(CamelContext.class), any(), any(),
+                any(), any(), any(), any(),
+                any(RestConfiguration.class), any(Map.class)))
+                .thenReturn(mockConsumer);
+
+        RestEndpoint endpoint = (RestEndpoint) camelContext.getEndpoint(
+                
"rest:get:users?host=localhost&consumerComponentName=myConsumerFactory");
+
+        Consumer consumer = endpoint.createConsumer(exchange -> {
+        });
+
+        assertThat(consumer).isNotNull();
+    }
+
+    @Test
+    void testCreateConsumerWithNonDefaultPort() throws Exception {
+        RestConfiguration restConfig = new RestConfiguration();
+        restConfig.setPort(9090);
+        camelContext.setRestConfiguration(restConfig);
+
+        registry.bind("myConsumerFactory", mockConsumerFactory);
+
+        when(mockConsumerFactory.createConsumer(
+                any(CamelContext.class), any(), any(),
+                any(), any(), any(), any(),
+                any(RestConfiguration.class), any(Map.class)))
+                .thenReturn(mockConsumer);
+
+        RestEndpoint endpoint = (RestEndpoint) camelContext.getEndpoint(
+                
"rest:get:users?host=localhost&consumerComponentName=myConsumerFactory");
+
+        Consumer consumer = endpoint.createConsumer(exchange -> {
+        });
+
+        assertThat(consumer).isNotNull();
+    }
+
+    @Test
+    void testCreateConsumerWithUriTemplate() throws Exception {
+        registry.bind("myConsumerFactory", mockConsumerFactory);
+
+        when(mockConsumerFactory.createConsumer(
+                any(CamelContext.class), any(), any(),
+                any(), any(), any(), any(),
+                any(RestConfiguration.class), any(Map.class)))
+                .thenReturn(mockConsumer);
+
+        RestEndpoint endpoint = (RestEndpoint) camelContext.getEndpoint(
+                
"rest:get:users:{id}?host=localhost&consumerComponentName=myConsumerFactory");
+
+        Consumer consumer = endpoint.createConsumer(exchange -> {
+        });
+
+        assertThat(consumer).isNotNull();
+        assertThat(endpoint.getUriTemplate()).isEqualTo("{id}");
+    }
+}
diff --git 
a/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestEndpointTest.java
 
b/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestEndpointTest.java
new file mode 100644
index 000000000000..e98ce4f6de33
--- /dev/null
+++ 
b/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestEndpointTest.java
@@ -0,0 +1,135 @@
+/*
+ * 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.rest;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.spi.RestConfiguration;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/**
+ * Tests for RestEndpoint focusing on behavior and error handling.
+ */
+class RestEndpointTest {
+
+    private CamelContext camelContext;
+    private RestComponent component;
+
+    @BeforeEach
+    void setUp() throws Exception {
+        camelContext = new DefaultCamelContext();
+        component = new RestComponent();
+        component.setCamelContext(camelContext);
+        camelContext.addComponent("rest", component);
+        camelContext.start();
+    }
+
+    @AfterEach
+    void tearDown() throws Exception {
+        camelContext.stop();
+    }
+
+    @Test
+    void testCreateProducerWithoutHostThrowsIllegalArgumentException() throws 
Exception {
+        RestEndpoint endpoint = new RestEndpoint("rest:get:users", component);
+        endpoint.setMethod("get");
+        endpoint.setPath("users");
+        endpoint.setParameters(new HashMap<>());
+
+        assertThatThrownBy(endpoint::createProducer)
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("Hostname must be configured");
+    }
+
+    @Test
+    void testCreateConsumerWithoutFactoryThrowsIllegalStateException() throws 
Exception {
+        RestEndpoint endpoint = (RestEndpoint) 
camelContext.getEndpoint("rest:get:users?host=localhost");
+
+        assertThatThrownBy(() -> endpoint.createConsumer(exchange -> {
+        }))
+                .isInstanceOf(IllegalStateException.class)
+                .hasMessageContaining("Cannot find RestConsumerFactory");
+    }
+
+    @Test
+    void testConfigurePropertiesMergesParametersMap() throws Exception {
+        RestEndpoint endpoint = (RestEndpoint) 
camelContext.getEndpoint("rest:get:users?host=localhost");
+
+        Map<String, Object> options = new HashMap<>();
+        Map<String, Object> params = new HashMap<>();
+        params.put("customKey", "customValue");
+        options.put("parameters", params);
+
+        endpoint.configureProperties(options);
+
+        assertThat(endpoint.getParameters()).containsEntry("customKey", 
"customValue");
+    }
+
+    @Test
+    void testBindingModeCanBeSetFromString() throws Exception {
+        RestEndpoint endpoint = (RestEndpoint) 
camelContext.getEndpoint("rest:get:users?host=localhost&bindingMode=auto");
+
+        
assertThat(endpoint.getBindingMode()).isEqualTo(RestConfiguration.RestBindingMode.auto);
+    }
+
+    @Test
+    void testBindingModeCanBeSetFromEnum() throws Exception {
+        RestEndpoint endpoint = (RestEndpoint) 
camelContext.getEndpoint("rest:get:users?host=localhost");
+
+        endpoint.setBindingMode(RestConfiguration.RestBindingMode.json);
+        
assertThat(endpoint.getBindingMode()).isEqualTo(RestConfiguration.RestBindingMode.json);
+
+        endpoint.setBindingMode("xml");
+        
assertThat(endpoint.getBindingMode()).isEqualTo(RestConfiguration.RestBindingMode.xml);
+    }
+
+    @Test
+    void testHostWithoutSchemeGetsHttpPrefixAdded() throws Exception {
+        RestEndpoint endpoint = (RestEndpoint) 
camelContext.getEndpoint("rest:get:users?host=localhost:8080");
+
+        assertThat(endpoint.getHost()).isEqualTo("http://localhost:8080";);
+    }
+
+    @Test
+    void testHostWithHttpSchemeIsPreserved() throws Exception {
+        RestEndpoint endpoint = (RestEndpoint) 
camelContext.getEndpoint("rest:get:users?host=http://localhost:8080";);
+
+        assertThat(endpoint.getHost()).isEqualTo("http://localhost:8080";);
+    }
+
+    @Test
+    void testHostWithHttpsSchemeIsPreserved() throws Exception {
+        RestEndpoint endpoint = (RestEndpoint) 
camelContext.getEndpoint("rest:get:users?host=https://localhost:8443";);
+
+        assertThat(endpoint.getHost()).isEqualTo("https://localhost:8443";);
+    }
+
+    @Test
+    void testIsLenientPropertiesReturnsTrue() throws Exception {
+        RestEndpoint endpoint = (RestEndpoint) 
camelContext.getEndpoint("rest:get:users?host=localhost");
+
+        assertThat(endpoint.isLenientProperties()).isTrue();
+    }
+}
diff --git 
a/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestProducerAdvancedTest.java
 
b/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestProducerAdvancedTest.java
new file mode 100644
index 000000000000..9680e1d5d00e
--- /dev/null
+++ 
b/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestProducerAdvancedTest.java
@@ -0,0 +1,334 @@
+/*
+ * 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.rest;
+
+import java.util.HashMap;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Exchange;
+import org.apache.camel.Producer;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.spi.RestConfiguration;
+import org.apache.camel.support.DefaultExchange;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@ExtendWith(MockitoExtension.class)
+class RestProducerAdvancedTest {
+
+    private CamelContext camelContext;
+    private RestComponent component;
+
+    @Mock
+    private Producer mockProducer;
+
+    @BeforeEach
+    void setUp() throws Exception {
+        camelContext = new DefaultCamelContext();
+        component = new RestComponent();
+        component.setCamelContext(camelContext);
+        camelContext.addComponent("rest", component);
+        camelContext.start();
+    }
+
+    @AfterEach
+    void tearDown() throws Exception {
+        camelContext.stop();
+    }
+
+    @Test
+    void testPrepareExchangeWithMultiplePathPlaceholders() throws Exception {
+        // The colon is used in the URI syntax as path:uriTemplate, so we test 
with slashes
+        RestEndpoint endpoint
+                = (RestEndpoint) 
camelContext.getEndpoint("rest:get:users/{userId}/orders/{orderId}?host=http://localhost";);
+        endpoint.setParameters(new HashMap<>());
+
+        RestConfiguration config = new RestConfiguration();
+        RestProducer producer = new RestProducer(endpoint, mockProducer, 
config);
+
+        Exchange exchange = new DefaultExchange(camelContext);
+        exchange.getMessage().setHeader("userId", "123");
+        exchange.getMessage().setHeader("orderId", "456");
+        producer.prepareExchange(exchange);
+
+        String uri = 
exchange.getMessage().getHeader(RestConstants.REST_HTTP_URI, String.class);
+        assertThat(uri).isEqualTo("http://localhost/users/123/orders/456";);
+    }
+
+    @Test
+    void testPrepareExchangeWithUnresolvedPlaceholder() throws Exception {
+        RestEndpoint endpoint = (RestEndpoint) 
camelContext.getEndpoint("rest:get:users:{userId}?host=http://localhost";);
+        endpoint.setParameters(new HashMap<>());
+
+        RestConfiguration config = new RestConfiguration();
+        RestProducer producer = new RestProducer(endpoint, mockProducer, 
config);
+
+        Exchange exchange = new DefaultExchange(camelContext);
+        // Don't set the header, so placeholder won't be resolved
+        producer.prepareExchange(exchange);
+
+        // When placeholder is not resolved, REST_HTTP_URI should not be set
+        String uri = 
exchange.getMessage().getHeader(RestConstants.REST_HTTP_URI, String.class);
+        assertThat(uri).isNull();
+    }
+
+    @Test
+    void testPrepareExchangeWithMalformedPlaceholder() throws Exception {
+        RestEndpoint endpoint = (RestEndpoint) 
camelContext.getEndpoint("rest:get:users:{userId?host=http://localhost";);
+        endpoint.setParameters(new HashMap<>());
+
+        RestConfiguration config = new RestConfiguration();
+        RestProducer producer = new RestProducer(endpoint, mockProducer, 
config);
+
+        Exchange exchange = new DefaultExchange(camelContext);
+        exchange.getMessage().setHeader("userId", "123");
+        producer.prepareExchange(exchange);
+
+        // Malformed placeholder with unclosed brace should not crash
+        assertThat(exchange.getException()).isNull();
+    }
+
+    @Test
+    void testPrepareExchangeWithPathOnly() throws Exception {
+        RestEndpoint endpoint = (RestEndpoint) 
camelContext.getEndpoint("rest:get:users?host=http://localhost";);
+        endpoint.setParameters(new HashMap<>());
+
+        RestConfiguration config = new RestConfiguration();
+        RestProducer producer = new RestProducer(endpoint, mockProducer, 
config);
+
+        Exchange exchange = new DefaultExchange(camelContext);
+        producer.prepareExchange(exchange);
+
+        // No template resolution needed, so no REST_HTTP_URI
+        String uri = 
exchange.getMessage().getHeader(RestConstants.REST_HTTP_URI, String.class);
+        assertThat(uri).isNull();
+    }
+
+    @Test
+    void testPrepareExchangeWithBasePathAndTemplate() throws Exception {
+        RestEndpoint endpoint = (RestEndpoint) 
camelContext.getEndpoint("rest:get:api/v1:users/{id}?host=http://localhost";);
+        endpoint.setParameters(new HashMap<>());
+
+        RestConfiguration config = new RestConfiguration();
+        RestProducer producer = new RestProducer(endpoint, mockProducer, 
config);
+
+        Exchange exchange = new DefaultExchange(camelContext);
+        exchange.getMessage().setHeader("id", "999");
+        producer.prepareExchange(exchange);
+
+        String uri = 
exchange.getMessage().getHeader(RestConstants.REST_HTTP_URI, String.class);
+        assertThat(uri).isEqualTo("http://localhost/api/v1/users/999";);
+    }
+
+    @Test
+    void testPrepareExchangeWithEmptyQueryParameters() throws Exception {
+        RestEndpoint endpoint = (RestEndpoint) 
camelContext.getEndpoint("rest:get:users?host=localhost");
+        endpoint.setQueryParameters("");
+        endpoint.setParameters(new HashMap<>());
+
+        RestConfiguration config = new RestConfiguration();
+        RestProducer producer = new RestProducer(endpoint, mockProducer, 
config);
+
+        Exchange exchange = new DefaultExchange(camelContext);
+        producer.prepareExchange(exchange);
+
+        String query = 
exchange.getMessage().getHeader(RestConstants.REST_HTTP_QUERY, String.class);
+        // Empty query parameters result in empty string, not null
+        assertThat(query).isEmpty();
+    }
+
+    @Test
+    void testPrepareExchangePreservesExistingHeaders() throws Exception {
+        RestEndpoint endpoint = (RestEndpoint) 
camelContext.getEndpoint("rest:post:users?host=localhost");
+        endpoint.setProduces("application/json");
+        endpoint.setConsumes("application/xml");
+        endpoint.setParameters(new HashMap<>());
+
+        RestConfiguration config = new RestConfiguration();
+        RestProducer producer = new RestProducer(endpoint, mockProducer, 
config);
+
+        Exchange exchange = new DefaultExchange(camelContext);
+        exchange.getMessage().setHeader("Custom-Header", "custom-value");
+        producer.prepareExchange(exchange);
+
+        
assertThat(exchange.getMessage().getHeader("Custom-Header")).isEqualTo("custom-value");
+        
assertThat(exchange.getMessage().getHeader(RestConstants.HTTP_METHOD)).isEqualTo("POST");
+    }
+
+    @Test
+    void testPrepareExchangeWithAllMethodTypes() throws Exception {
+        String[] methods = { "get", "post", "put", "delete", "patch", "head", 
"options", "trace", "connect" };
+
+        for (String method : methods) {
+            RestEndpoint endpoint = (RestEndpoint) 
camelContext.getEndpoint("rest:" + method + ":test?host=localhost");
+            endpoint.setParameters(new HashMap<>());
+
+            RestConfiguration config = new RestConfiguration();
+            RestProducer producer = new RestProducer(endpoint, mockProducer, 
config);
+
+            Exchange exchange = new DefaultExchange(camelContext);
+            producer.prepareExchange(exchange);
+
+            
assertThat(exchange.getMessage().getHeader(RestConstants.HTTP_METHOD))
+                    .isEqualTo(method.toUpperCase());
+        }
+    }
+
+    @Test
+    void testPrepareExchangeWithNullMethod() throws Exception {
+        RestEndpoint endpoint = new RestEndpoint("rest:null:test", component);
+        endpoint.setPath("test");
+        endpoint.setMethod(null);
+        endpoint.setHost("http://localhost";);
+        endpoint.setParameters(new HashMap<>());
+
+        RestConfiguration config = new RestConfiguration();
+        RestProducer producer = new RestProducer(endpoint, mockProducer, 
config);
+
+        Exchange exchange = new DefaultExchange(camelContext);
+        producer.prepareExchange(exchange);
+
+        // Method is null, so HTTP_METHOD should not be set
+        
assertThat(exchange.getMessage().getHeader(RestConstants.HTTP_METHOD)).isNull();
+    }
+
+    @Test
+    void testPrepareExchangeWithNullProducesAndConsumes() throws Exception {
+        RestEndpoint endpoint = (RestEndpoint) 
camelContext.getEndpoint("rest:get:users?host=localhost");
+        endpoint.setProduces(null);
+        endpoint.setConsumes(null);
+        endpoint.setParameters(new HashMap<>());
+
+        RestConfiguration config = new RestConfiguration();
+        RestProducer producer = new RestProducer(endpoint, mockProducer, 
config);
+
+        Exchange exchange = new DefaultExchange(camelContext);
+        producer.prepareExchange(exchange);
+
+        // No content-type or accept headers should be set
+        
assertThat(exchange.getMessage().getHeader(RestConstants.CONTENT_TYPE)).isNull();
+        
assertThat(exchange.getMessage().getHeader(RestConstants.ACCEPT)).isNull();
+    }
+
+    @Test
+    void testProcessWithExceptionDuringPrepare() throws Exception {
+        RestEndpoint endpoint = (RestEndpoint) 
camelContext.getEndpoint("rest:get:users?host=localhost");
+        // Set query parameters that will cause an exception during parsing
+        endpoint.setQueryParameters("invalid=%%");
+        endpoint.setParameters(new HashMap<>());
+
+        RestConfiguration config = new RestConfiguration();
+        RestProducer producer = new RestProducer(endpoint, mockProducer, 
config);
+
+        Exchange exchange = new DefaultExchange(camelContext);
+        boolean[] callbackDone = { false };
+
+        boolean result = producer.process(exchange, doneSync -> 
callbackDone[0] = true);
+
+        assertThat(result).isTrue();
+        assertThat(callbackDone[0]).isTrue();
+        assertThat(exchange.getException()).isNotNull();
+    }
+
+    @Test
+    void testRestProducerLifecycle() throws Exception {
+        RestEndpoint endpoint = (RestEndpoint) 
camelContext.getEndpoint("rest:get:users?host=localhost");
+        endpoint.setParameters(new HashMap<>());
+
+        RestConfiguration config = new RestConfiguration();
+        config.setBindingMode(RestConfiguration.RestBindingMode.off);
+        RestProducer producer = new RestProducer(endpoint, mockProducer, 
config);
+
+        // Test lifecycle
+        producer.doInit();
+        producer.doStart();
+
+        assertThat(producer.getEndpoint()).isSameAs(endpoint);
+
+        producer.doStop();
+    }
+
+    @Test
+    void testCreateQueryParametersWithTrailingAmpersand() throws Exception {
+        Exchange exchange = new DefaultExchange(camelContext);
+        exchange.getMessage().setHeader("a", "1");
+        // b is optional and not set
+
+        String result = RestProducer.createQueryParameters("a={a}&b={b?}", 
exchange);
+
+        assertThat(result).isEqualTo("a=1");
+        assertThat(result).doesNotEndWith("&");
+    }
+
+    @Test
+    void testCreateQueryParametersWithAllOptionalMissing() throws Exception {
+        Exchange exchange = new DefaultExchange(camelContext);
+        // Don't set any headers
+
+        String result = 
RestProducer.createQueryParameters("a={a?}&b={b?}&c={c?}", exchange);
+
+        assertThat(result).isEmpty();
+    }
+
+    @Test
+    void testCreateQueryParametersWithMixedValues() throws Exception {
+        Exchange exchange = new DefaultExchange(camelContext);
+        exchange.getMessage().setHeader("a", "value1");
+        exchange.setVariable("c", "value3");
+        // b is optional and not set
+
+        String result = 
RestProducer.createQueryParameters("a={a}&b={b?}&c={c}", exchange);
+
+        assertThat(result).contains("a=value1");
+        assertThat(result).contains("c=value3");
+        assertThat(result).doesNotContain("b=");
+    }
+
+    @Test
+    void testCreateQueryParametersWithSpecialCharacters() throws Exception {
+        Exchange exchange = new DefaultExchange(camelContext);
+        exchange.getMessage().setHeader("name", "John Doe");
+        exchange.getMessage().setHeader("email", "[email protected]");
+
+        String result = 
RestProducer.createQueryParameters("name={name}&email={email}", exchange);
+
+        assertThat(result).contains("name=John+Doe");
+        assertThat(result).contains("email=john%40example.com");
+    }
+
+    @Test
+    void testPrepareExchangeWithLeadingSlashInUriTemplate() throws Exception {
+        RestEndpoint endpoint = (RestEndpoint) 
camelContext.getEndpoint("rest:get:api:/{id}?host=http://localhost";);
+        endpoint.setParameters(new HashMap<>());
+
+        RestConfiguration config = new RestConfiguration();
+        RestProducer producer = new RestProducer(endpoint, mockProducer, 
config);
+
+        Exchange exchange = new DefaultExchange(camelContext);
+        exchange.getMessage().setHeader("id", "123");
+        producer.prepareExchange(exchange);
+
+        String uri = 
exchange.getMessage().getHeader(RestConstants.REST_HTTP_URI, String.class);
+        assertThat(uri).isEqualTo("http://localhost/api/123";);
+    }
+}
diff --git 
a/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestProducerBindingCallbackTest.java
 
b/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestProducerBindingCallbackTest.java
new file mode 100644
index 000000000000..2347e02ece73
--- /dev/null
+++ 
b/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestProducerBindingCallbackTest.java
@@ -0,0 +1,402 @@
+/*
+ * 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.rest;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.camel.AsyncCallback;
+import org.apache.camel.AsyncProcessor;
+import org.apache.camel.CamelContext;
+import org.apache.camel.Exchange;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.spi.DataFormat;
+import org.apache.camel.support.DefaultExchange;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+class RestProducerBindingCallbackTest {
+
+    private CamelContext camelContext;
+
+    @Mock
+    private AsyncProcessor mockProcessor;
+
+    @Mock
+    private DataFormat jsonDataFormat;
+
+    @Mock
+    private DataFormat xmlDataFormat;
+
+    @Mock
+    private DataFormat outJsonDataFormat;
+
+    @Mock
+    private DataFormat outXmlDataFormat;
+
+    @BeforeEach
+    void setUp() throws Exception {
+        camelContext = new DefaultCamelContext();
+        camelContext.start();
+    }
+
+    @AfterEach
+    void tearDown() throws Exception {
+        camelContext.stop();
+    }
+
+    @Test
+    void testCallbackWithJsonResponse() {
+        AtomicBoolean callbackCalled = new AtomicBoolean(false);
+
+        doAnswer(invocation -> {
+            Exchange exchange = invocation.getArgument(0);
+            AsyncCallback callback = invocation.getArgument(1);
+            exchange.getMessage().setBody("{\"result\": \"ok\"}");
+            exchange.getMessage().setHeader(RestConstants.CONTENT_TYPE, 
"application/json");
+            callback.done(true);
+            return true;
+        }).when(mockProcessor).process(any(Exchange.class), 
any(AsyncCallback.class));
+
+        RestProducerBindingProcessor processor = new 
RestProducerBindingProcessor(
+                mockProcessor, camelContext, jsonDataFormat, xmlDataFormat,
+                outJsonDataFormat, outXmlDataFormat, "json", true, 
"com.example.Response");
+
+        Exchange exchange = new DefaultExchange(camelContext);
+        exchange.getMessage().setBody(null);
+
+        processor.process(exchange, doneSync -> callbackCalled.set(true));
+
+        assertThat(callbackCalled.get()).isTrue();
+    }
+
+    @Test
+    void testCallbackWithXmlResponse() {
+        AtomicBoolean callbackCalled = new AtomicBoolean(false);
+
+        doAnswer(invocation -> {
+            Exchange exchange = invocation.getArgument(0);
+            AsyncCallback callback = invocation.getArgument(1);
+            exchange.getMessage().setBody("<result>ok</result>");
+            exchange.getMessage().setHeader(RestConstants.CONTENT_TYPE, 
"application/xml");
+            callback.done(true);
+            return true;
+        }).when(mockProcessor).process(any(Exchange.class), 
any(AsyncCallback.class));
+
+        RestProducerBindingProcessor processor = new 
RestProducerBindingProcessor(
+                mockProcessor, camelContext, jsonDataFormat, xmlDataFormat,
+                outJsonDataFormat, outXmlDataFormat, "xml", true, 
"com.example.Response");
+
+        Exchange exchange = new DefaultExchange(camelContext);
+        exchange.getMessage().setBody(null);
+
+        processor.process(exchange, doneSync -> callbackCalled.set(true));
+
+        assertThat(callbackCalled.get()).isTrue();
+    }
+
+    @Test
+    void testCallbackSkipsBindingOnErrorCode() {
+        AtomicBoolean callbackCalled = new AtomicBoolean(false);
+
+        doAnswer(invocation -> {
+            Exchange exchange = invocation.getArgument(0);
+            AsyncCallback callback = invocation.getArgument(1);
+            exchange.getMessage().setBody("Error");
+            exchange.getMessage().setHeader(RestConstants.HTTP_RESPONSE_CODE, 
500);
+            exchange.getMessage().setHeader(RestConstants.CONTENT_TYPE, 
"application/json");
+            callback.done(true);
+            return true;
+        }).when(mockProcessor).process(any(Exchange.class), 
any(AsyncCallback.class));
+
+        RestProducerBindingProcessor processor = new 
RestProducerBindingProcessor(
+                mockProcessor, camelContext, jsonDataFormat, xmlDataFormat,
+                outJsonDataFormat, outXmlDataFormat, "json", true, 
"com.example.Response");
+
+        Exchange exchange = new DefaultExchange(camelContext);
+        exchange.getMessage().setBody(null);
+
+        processor.process(exchange, doneSync -> callbackCalled.set(true));
+
+        assertThat(callbackCalled.get()).isTrue();
+        // Body should not have been transformed due to error code skip
+        
assertThat(exchange.getMessage().getBody(String.class)).isEqualTo("Error");
+    }
+
+    @Test
+    void testCallbackWithEmptyResponseBody() {
+        AtomicBoolean callbackCalled = new AtomicBoolean(false);
+
+        doAnswer(invocation -> {
+            Exchange exchange = invocation.getArgument(0);
+            AsyncCallback callback = invocation.getArgument(1);
+            exchange.getMessage().setBody(null);
+            exchange.getMessage().setHeader(RestConstants.CONTENT_TYPE, 
"application/json");
+            callback.done(true);
+            return true;
+        }).when(mockProcessor).process(any(Exchange.class), 
any(AsyncCallback.class));
+
+        RestProducerBindingProcessor processor = new 
RestProducerBindingProcessor(
+                mockProcessor, camelContext, jsonDataFormat, xmlDataFormat,
+                outJsonDataFormat, outXmlDataFormat, "json", true, 
"com.example.Response");
+
+        Exchange exchange = new DefaultExchange(camelContext);
+        exchange.getMessage().setBody(null);
+
+        processor.process(exchange, doneSync -> callbackCalled.set(true));
+
+        assertThat(callbackCalled.get()).isTrue();
+    }
+
+    @Test
+    void testCallbackWithExceptionInExchange() {
+        AtomicBoolean callbackCalled = new AtomicBoolean(false);
+
+        doAnswer(invocation -> {
+            Exchange exchange = invocation.getArgument(0);
+            AsyncCallback callback = invocation.getArgument(1);
+            exchange.setException(new RuntimeException("Test error"));
+            callback.done(true);
+            return true;
+        }).when(mockProcessor).process(any(Exchange.class), 
any(AsyncCallback.class));
+
+        RestProducerBindingProcessor processor = new 
RestProducerBindingProcessor(
+                mockProcessor, camelContext, jsonDataFormat, xmlDataFormat,
+                outJsonDataFormat, outXmlDataFormat, "json", true, 
"com.example.Response");
+
+        Exchange exchange = new DefaultExchange(camelContext);
+        exchange.getMessage().setBody(null);
+
+        processor.process(exchange, doneSync -> callbackCalled.set(true));
+
+        assertThat(callbackCalled.get()).isTrue();
+        assertThat(exchange.getException()).isNotNull();
+    }
+
+    @Test
+    void testCallbackWithBindingModeOff() {
+        AtomicBoolean callbackCalled = new AtomicBoolean(false);
+
+        doAnswer(invocation -> {
+            Exchange exchange = invocation.getArgument(0);
+            AsyncCallback callback = invocation.getArgument(1);
+            exchange.getMessage().setBody("{\"result\": \"ok\"}");
+            exchange.getMessage().setHeader(RestConstants.CONTENT_TYPE, 
"application/json");
+            callback.done(true);
+            return true;
+        }).when(mockProcessor).process(any(Exchange.class), 
any(AsyncCallback.class));
+
+        RestProducerBindingProcessor processor = new 
RestProducerBindingProcessor(
+                mockProcessor, camelContext, jsonDataFormat, xmlDataFormat,
+                outJsonDataFormat, outXmlDataFormat, "off", true, 
"com.example.Response");
+
+        Exchange exchange = new DefaultExchange(camelContext);
+        exchange.getMessage().setBody(null);
+
+        processor.process(exchange, doneSync -> callbackCalled.set(true));
+
+        assertThat(callbackCalled.get()).isTrue();
+    }
+
+    @Test
+    void testCallbackWithBindingModeAuto() {
+        AtomicBoolean callbackCalled = new AtomicBoolean(false);
+
+        doAnswer(invocation -> {
+            Exchange exchange = invocation.getArgument(0);
+            AsyncCallback callback = invocation.getArgument(1);
+            exchange.getMessage().setBody("{\"result\": \"ok\"}");
+            exchange.getMessage().setHeader(RestConstants.CONTENT_TYPE, 
"application/json");
+            callback.done(true);
+            return true;
+        }).when(mockProcessor).process(any(Exchange.class), 
any(AsyncCallback.class));
+
+        RestProducerBindingProcessor processor = new 
RestProducerBindingProcessor(
+                mockProcessor, camelContext, jsonDataFormat, xmlDataFormat,
+                outJsonDataFormat, outXmlDataFormat, "auto", true, 
"com.example.Response");
+
+        Exchange exchange = new DefaultExchange(camelContext);
+        exchange.getMessage().setBody(null);
+
+        processor.process(exchange, doneSync -> callbackCalled.set(true));
+
+        assertThat(callbackCalled.get()).isTrue();
+    }
+
+    @Test
+    void testCallbackWithNoContentType() {
+        AtomicBoolean callbackCalled = new AtomicBoolean(false);
+
+        doAnswer(invocation -> {
+            Exchange exchange = invocation.getArgument(0);
+            AsyncCallback callback = invocation.getArgument(1);
+            exchange.getMessage().setBody("{\"result\": \"ok\"}");
+            // No content type set
+            callback.done(true);
+            return true;
+        }).when(mockProcessor).process(any(Exchange.class), 
any(AsyncCallback.class));
+
+        RestProducerBindingProcessor processor = new 
RestProducerBindingProcessor(
+                mockProcessor, camelContext, jsonDataFormat, xmlDataFormat,
+                outJsonDataFormat, outXmlDataFormat, "json", true, 
"com.example.Response");
+
+        Exchange exchange = new DefaultExchange(camelContext);
+        exchange.getMessage().setBody(null);
+
+        processor.process(exchange, doneSync -> callbackCalled.set(true));
+
+        assertThat(callbackCalled.get()).isTrue();
+        // Content-Type should be set by the callback
+        
assertThat(exchange.getMessage().getHeader(RestConstants.CONTENT_TYPE)).isEqualTo("application/json");
+    }
+
+    @Test
+    void testCallbackWithXmlContentTypeWhenJsonMode() {
+        AtomicBoolean callbackCalled = new AtomicBoolean(false);
+
+        doAnswer(invocation -> {
+            Exchange exchange = invocation.getArgument(0);
+            AsyncCallback callback = invocation.getArgument(1);
+            exchange.getMessage().setBody("<result>ok</result>");
+            exchange.getMessage().setHeader(RestConstants.CONTENT_TYPE, 
"application/xml");
+            callback.done(true);
+            return true;
+        }).when(mockProcessor).process(any(Exchange.class), 
any(AsyncCallback.class));
+
+        RestProducerBindingProcessor processor = new 
RestProducerBindingProcessor(
+                mockProcessor, camelContext, jsonDataFormat, xmlDataFormat,
+                outJsonDataFormat, outXmlDataFormat, "json", true, 
"com.example.Response");
+
+        Exchange exchange = new DefaultExchange(camelContext);
+        exchange.getMessage().setBody(null);
+
+        processor.process(exchange, doneSync -> callbackCalled.set(true));
+
+        assertThat(callbackCalled.get()).isTrue();
+    }
+
+    @Test
+    void testCallbackWithJsonXmlBindingMode() {
+        AtomicBoolean callbackCalled = new AtomicBoolean(false);
+
+        doAnswer(invocation -> {
+            Exchange exchange = invocation.getArgument(0);
+            AsyncCallback callback = invocation.getArgument(1);
+            exchange.getMessage().setBody("{\"result\": \"ok\"}");
+            exchange.getMessage().setHeader(RestConstants.CONTENT_TYPE, 
"application/json");
+            callback.done(true);
+            return true;
+        }).when(mockProcessor).process(any(Exchange.class), 
any(AsyncCallback.class));
+
+        RestProducerBindingProcessor processor = new 
RestProducerBindingProcessor(
+                mockProcessor, camelContext, jsonDataFormat, xmlDataFormat,
+                outJsonDataFormat, outXmlDataFormat, "json_xml", true, 
"com.example.Response");
+
+        Exchange exchange = new DefaultExchange(camelContext);
+        exchange.getMessage().setBody(null);
+
+        processor.process(exchange, doneSync -> callbackCalled.set(true));
+
+        assertThat(callbackCalled.get()).isTrue();
+    }
+
+    @Test
+    void testCallbackWith3xxResponseCode() {
+        AtomicBoolean callbackCalled = new AtomicBoolean(false);
+
+        doAnswer(invocation -> {
+            Exchange exchange = invocation.getArgument(0);
+            AsyncCallback callback = invocation.getArgument(1);
+            exchange.getMessage().setBody("Redirect");
+            exchange.getMessage().setHeader(RestConstants.HTTP_RESPONSE_CODE, 
302);
+            exchange.getMessage().setHeader(RestConstants.CONTENT_TYPE, 
"application/json");
+            callback.done(true);
+            return true;
+        }).when(mockProcessor).process(any(Exchange.class), 
any(AsyncCallback.class));
+
+        RestProducerBindingProcessor processor = new 
RestProducerBindingProcessor(
+                mockProcessor, camelContext, jsonDataFormat, xmlDataFormat,
+                outJsonDataFormat, outXmlDataFormat, "json", true, 
"com.example.Response");
+
+        Exchange exchange = new DefaultExchange(camelContext);
+        exchange.getMessage().setBody(null);
+
+        processor.process(exchange, doneSync -> callbackCalled.set(true));
+
+        assertThat(callbackCalled.get()).isTrue();
+    }
+
+    @Test
+    void testCallbackWithNoUnmarshallers() {
+        AtomicBoolean callbackCalled = new AtomicBoolean(false);
+
+        doAnswer(invocation -> {
+            Exchange exchange = invocation.getArgument(0);
+            AsyncCallback callback = invocation.getArgument(1);
+            exchange.getMessage().setBody("{\"result\": \"ok\"}");
+            exchange.getMessage().setHeader(RestConstants.CONTENT_TYPE, 
"application/json");
+            callback.done(true);
+            return true;
+        }).when(mockProcessor).process(any(Exchange.class), 
any(AsyncCallback.class));
+
+        RestProducerBindingProcessor processor = new 
RestProducerBindingProcessor(
+                mockProcessor, camelContext, null, null,
+                null, null, "json", true, "com.example.Response");
+
+        Exchange exchange = new DefaultExchange(camelContext);
+        exchange.getMessage().setBody(null);
+
+        processor.process(exchange, doneSync -> callbackCalled.set(true));
+
+        assertThat(callbackCalled.get()).isTrue();
+    }
+
+    @Test
+    void testCallbackWithSkipBindingOnErrorCodeDisabled() {
+        AtomicBoolean callbackCalled = new AtomicBoolean(false);
+
+        doAnswer(invocation -> {
+            Exchange exchange = invocation.getArgument(0);
+            AsyncCallback callback = invocation.getArgument(1);
+            exchange.getMessage().setBody("Error");
+            exchange.getMessage().setHeader(RestConstants.HTTP_RESPONSE_CODE, 
400);
+            exchange.getMessage().setHeader(RestConstants.CONTENT_TYPE, 
"application/json");
+            callback.done(true);
+            return true;
+        }).when(mockProcessor).process(any(Exchange.class), 
any(AsyncCallback.class));
+
+        RestProducerBindingProcessor processor = new 
RestProducerBindingProcessor(
+                mockProcessor, camelContext, jsonDataFormat, xmlDataFormat,
+                outJsonDataFormat, outXmlDataFormat, "json", false, 
"com.example.Response");
+
+        Exchange exchange = new DefaultExchange(camelContext);
+        exchange.getMessage().setBody(null);
+
+        processor.process(exchange, doneSync -> callbackCalled.set(true));
+
+        assertThat(callbackCalled.get()).isTrue();
+    }
+}
diff --git 
a/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestProducerBindingProcessorTest.java
 
b/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestProducerBindingProcessorTest.java
new file mode 100644
index 000000000000..dc9f7a3e9659
--- /dev/null
+++ 
b/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestProducerBindingProcessorTest.java
@@ -0,0 +1,311 @@
+/*
+ * 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.rest;
+
+import java.io.ByteArrayInputStream;
+import java.nio.charset.StandardCharsets;
+
+import org.apache.camel.AsyncCallback;
+import org.apache.camel.AsyncProcessor;
+import org.apache.camel.CamelContext;
+import org.apache.camel.Exchange;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.spi.DataFormat;
+import org.apache.camel.support.DefaultExchange;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+class RestProducerBindingProcessorTest {
+
+    private CamelContext camelContext;
+
+    @Mock
+    private AsyncProcessor mockProcessor;
+
+    @Mock
+    private DataFormat jsonDataFormat;
+
+    @Mock
+    private DataFormat xmlDataFormat;
+
+    @Mock
+    private DataFormat outJsonDataFormat;
+
+    @Mock
+    private DataFormat outXmlDataFormat;
+
+    @BeforeEach
+    void setUp() throws Exception {
+        camelContext = new DefaultCamelContext();
+        camelContext.start();
+    }
+
+    @AfterEach
+    void tearDown() throws Exception {
+        camelContext.stop();
+    }
+
+    @Test
+    void testProcessWithEmptyBody() {
+        when(mockProcessor.process(any(Exchange.class), 
any(AsyncCallback.class))).thenReturn(true);
+
+        RestProducerBindingProcessor processor = new 
RestProducerBindingProcessor(
+                mockProcessor, camelContext, jsonDataFormat, xmlDataFormat,
+                outJsonDataFormat, outXmlDataFormat, "json", true, null);
+
+        Exchange exchange = new DefaultExchange(camelContext);
+        exchange.getMessage().setBody(null);
+
+        boolean result = processor.process(exchange, doneSync -> {
+        });
+
+        assertThat(result).isTrue();
+    }
+
+    @Test
+    void testProcessWithStringBody() {
+        when(mockProcessor.process(any(Exchange.class), 
any(AsyncCallback.class))).thenReturn(true);
+
+        RestProducerBindingProcessor processor = new 
RestProducerBindingProcessor(
+                mockProcessor, camelContext, jsonDataFormat, xmlDataFormat,
+                outJsonDataFormat, outXmlDataFormat, "json", true, null);
+
+        Exchange exchange = new DefaultExchange(camelContext);
+        exchange.getMessage().setBody("{\"name\": \"test\"}");
+
+        boolean result = processor.process(exchange, doneSync -> {
+        });
+
+        assertThat(result).isTrue();
+    }
+
+    @Test
+    void testProcessWithByteArrayBody() {
+        when(mockProcessor.process(any(Exchange.class), 
any(AsyncCallback.class))).thenReturn(true);
+
+        RestProducerBindingProcessor processor = new 
RestProducerBindingProcessor(
+                mockProcessor, camelContext, jsonDataFormat, xmlDataFormat,
+                outJsonDataFormat, outXmlDataFormat, "json", true, null);
+
+        Exchange exchange = new DefaultExchange(camelContext);
+        exchange.getMessage().setBody("{\"name\": 
\"test\"}".getBytes(StandardCharsets.UTF_8));
+
+        boolean result = processor.process(exchange, doneSync -> {
+        });
+
+        assertThat(result).isTrue();
+    }
+
+    @Test
+    void testProcessWithInputStreamBody() {
+        when(mockProcessor.process(any(Exchange.class), 
any(AsyncCallback.class))).thenReturn(true);
+
+        RestProducerBindingProcessor processor = new 
RestProducerBindingProcessor(
+                mockProcessor, camelContext, jsonDataFormat, xmlDataFormat,
+                outJsonDataFormat, outXmlDataFormat, "json", true, null);
+
+        Exchange exchange = new DefaultExchange(camelContext);
+        exchange.getMessage().setBody(new ByteArrayInputStream("{\"name\": 
\"test\"}".getBytes(StandardCharsets.UTF_8)));
+
+        boolean result = processor.process(exchange, doneSync -> {
+        });
+
+        assertThat(result).isTrue();
+    }
+
+    @Test
+    void testProcessWithBindingModeOff() {
+        when(mockProcessor.process(any(Exchange.class), 
any(AsyncCallback.class))).thenReturn(true);
+
+        RestProducerBindingProcessor processor = new 
RestProducerBindingProcessor(
+                mockProcessor, camelContext, null, null, null, null, "off", 
true, null);
+
+        Exchange exchange = new DefaultExchange(camelContext);
+        exchange.getMessage().setBody(new Object());
+
+        boolean result = processor.process(exchange, doneSync -> {
+        });
+
+        assertThat(result).isTrue();
+    }
+
+    @Test
+    void testProcessWithBindingModeAuto() {
+        when(mockProcessor.process(any(Exchange.class), 
any(AsyncCallback.class))).thenReturn(true);
+
+        RestProducerBindingProcessor processor = new 
RestProducerBindingProcessor(
+                mockProcessor, camelContext, null, null, null, null, "auto", 
true, null);
+
+        Exchange exchange = new DefaultExchange(camelContext);
+        exchange.getMessage().setBody(new Object());
+
+        boolean result = processor.process(exchange, doneSync -> {
+        });
+
+        assertThat(result).isTrue();
+    }
+
+    @Test
+    void testProcessWithJsonContentType() {
+        when(mockProcessor.process(any(Exchange.class), 
any(AsyncCallback.class))).thenReturn(true);
+
+        RestProducerBindingProcessor processor = new 
RestProducerBindingProcessor(
+                mockProcessor, camelContext, jsonDataFormat, xmlDataFormat,
+                outJsonDataFormat, outXmlDataFormat, "auto", true, null);
+
+        Exchange exchange = new DefaultExchange(camelContext);
+        exchange.getMessage().setBody("test");
+        exchange.getMessage().setHeader(RestConstants.CONTENT_TYPE, 
"application/json");
+
+        boolean result = processor.process(exchange, doneSync -> {
+        });
+
+        assertThat(result).isTrue();
+    }
+
+    @Test
+    void testProcessWithXmlContentType() {
+        when(mockProcessor.process(any(Exchange.class), 
any(AsyncCallback.class))).thenReturn(true);
+
+        RestProducerBindingProcessor processor = new 
RestProducerBindingProcessor(
+                mockProcessor, camelContext, jsonDataFormat, xmlDataFormat,
+                outJsonDataFormat, outXmlDataFormat, "auto", true, null);
+
+        Exchange exchange = new DefaultExchange(camelContext);
+        exchange.getMessage().setBody("test");
+        exchange.getMessage().setHeader(RestConstants.CONTENT_TYPE, 
"application/xml");
+
+        boolean result = processor.process(exchange, doneSync -> {
+        });
+
+        assertThat(result).isTrue();
+    }
+
+    @Test
+    void testProcessWithOutType() {
+        when(mockProcessor.process(any(Exchange.class), 
any(AsyncCallback.class))).thenReturn(true);
+
+        RestProducerBindingProcessor processor = new 
RestProducerBindingProcessor(
+                mockProcessor, camelContext, jsonDataFormat, xmlDataFormat,
+                outJsonDataFormat, outXmlDataFormat, "json", true, 
"com.example.Response");
+
+        Exchange exchange = new DefaultExchange(camelContext);
+        exchange.getMessage().setBody(null);
+
+        boolean result = processor.process(exchange, doneSync -> {
+        });
+
+        assertThat(result).isTrue();
+    }
+
+    @Test
+    void testProcessWithSkipBindingOnErrorCodeEnabled() {
+        when(mockProcessor.process(any(Exchange.class), 
any(AsyncCallback.class))).thenReturn(true);
+
+        RestProducerBindingProcessor processor = new 
RestProducerBindingProcessor(
+                mockProcessor, camelContext, jsonDataFormat, xmlDataFormat,
+                outJsonDataFormat, outXmlDataFormat, "json", true, 
"com.example.Response");
+
+        Exchange exchange = new DefaultExchange(camelContext);
+        exchange.getMessage().setBody("test");
+        exchange.getMessage().setHeader(RestConstants.HTTP_RESPONSE_CODE, 500);
+
+        boolean result = processor.process(exchange, doneSync -> {
+        });
+
+        assertThat(result).isTrue();
+    }
+
+    @Test
+    void testProcessWithSkipBindingOnErrorCodeDisabled() {
+        when(mockProcessor.process(any(Exchange.class), 
any(AsyncCallback.class))).thenReturn(true);
+
+        RestProducerBindingProcessor processor = new 
RestProducerBindingProcessor(
+                mockProcessor, camelContext, jsonDataFormat, xmlDataFormat,
+                outJsonDataFormat, outXmlDataFormat, "json", false, 
"com.example.Response");
+
+        Exchange exchange = new DefaultExchange(camelContext);
+        exchange.getMessage().setBody("test");
+        exchange.getMessage().setHeader(RestConstants.HTTP_RESPONSE_CODE, 500);
+
+        boolean result = processor.process(exchange, doneSync -> {
+        });
+
+        assertThat(result).isTrue();
+    }
+
+    @Test
+    void testProcessWithNoDataFormats() {
+        when(mockProcessor.process(any(Exchange.class), 
any(AsyncCallback.class))).thenReturn(true);
+
+        RestProducerBindingProcessor processor = new 
RestProducerBindingProcessor(
+                mockProcessor, camelContext, null, null, null, null, "json", 
true, null);
+
+        Exchange exchange = new DefaultExchange(camelContext);
+        exchange.getMessage().setBody("plain text");
+
+        boolean result = processor.process(exchange, doneSync -> {
+        });
+
+        assertThat(result).isTrue();
+    }
+
+    @Test
+    void testProcessWithJsonBindingModeNoXml() {
+        when(mockProcessor.process(any(Exchange.class), 
any(AsyncCallback.class))).thenReturn(true);
+
+        RestProducerBindingProcessor processor = new 
RestProducerBindingProcessor(
+                mockProcessor, camelContext, jsonDataFormat, null,
+                outJsonDataFormat, null, "json", true, null);
+
+        Exchange exchange = new DefaultExchange(camelContext);
+        exchange.getMessage().setBody("test");
+        exchange.getMessage().setHeader(RestConstants.CONTENT_TYPE, 
"application/json");
+
+        boolean result = processor.process(exchange, doneSync -> {
+        });
+
+        assertThat(result).isTrue();
+    }
+
+    @Test
+    void testProcessWithXmlBindingModeNoJson() {
+        when(mockProcessor.process(any(Exchange.class), 
any(AsyncCallback.class))).thenReturn(true);
+
+        RestProducerBindingProcessor processor = new 
RestProducerBindingProcessor(
+                mockProcessor, camelContext, null, xmlDataFormat,
+                null, outXmlDataFormat, "xml", true, null);
+
+        Exchange exchange = new DefaultExchange(camelContext);
+        exchange.getMessage().setBody("test");
+        exchange.getMessage().setHeader(RestConstants.CONTENT_TYPE, 
"application/xml");
+
+        boolean result = processor.process(exchange, doneSync -> {
+        });
+
+        assertThat(result).isTrue();
+    }
+}
diff --git 
a/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestProducerTest.java
 
b/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestProducerTest.java
new file mode 100644
index 000000000000..46f404028766
--- /dev/null
+++ 
b/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestProducerTest.java
@@ -0,0 +1,354 @@
+/*
+ * 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.rest;
+
+import java.net.URISyntaxException;
+import java.util.HashMap;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Exchange;
+import org.apache.camel.Producer;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.spi.RestConfiguration;
+import org.apache.camel.support.DefaultExchange;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests for RestProducer focusing on query parameter resolution and exchange 
preparation.
+ */
+@ExtendWith(MockitoExtension.class)
+class RestProducerTest {
+
+    private CamelContext camelContext;
+    private RestComponent component;
+
+    @Mock
+    private Producer mockProducer;
+
+    @BeforeEach
+    void setUp() throws Exception {
+        camelContext = new DefaultCamelContext();
+        component = new RestComponent();
+        component.setCamelContext(camelContext);
+        camelContext.addComponent("rest", component);
+        camelContext.start();
+    }
+
+    @AfterEach
+    void tearDown() throws Exception {
+        camelContext.stop();
+    }
+
+    // ==================== Query Parameter Resolution Tests 
====================
+
+    @Test
+    void testCreateQueryParametersReturnsUnmodifiedQueryWhenNoPlaceholders() 
throws URISyntaxException {
+        Exchange exchange = new DefaultExchange(camelContext);
+
+        String result = RestProducer.createQueryParameters("page=1&size=10", 
exchange);
+
+        assertThat(result).isEqualTo("page=1&size=10");
+    }
+
+    @Test
+    void testCreateQueryParametersResolvesPlaceholderFromHeader() throws 
URISyntaxException {
+        Exchange exchange = new DefaultExchange(camelContext);
+        exchange.getMessage().setHeader("userId", "123");
+
+        String result = RestProducer.createQueryParameters("id={userId}", 
exchange);
+
+        assertThat(result).isEqualTo("id=123");
+    }
+
+    @Test
+    void testCreateQueryParametersResolvesMultiplePlaceholders() throws 
URISyntaxException {
+        Exchange exchange = new DefaultExchange(camelContext);
+        exchange.getMessage().setHeader("page", "2");
+        exchange.getMessage().setHeader("size", "50");
+
+        String result = 
RestProducer.createQueryParameters("page={page}&size={size}", exchange);
+
+        assertThat(result).isEqualTo("page=2&size=50");
+    }
+
+    @Test
+    void testCreateQueryParametersRemovesOptionalPlaceholderWhenNotSet() 
throws URISyntaxException {
+        Exchange exchange = new DefaultExchange(camelContext);
+
+        String result = RestProducer.createQueryParameters("filter={filter?}", 
exchange);
+
+        assertThat(result).isEmpty();
+    }
+
+    @Test
+    void testCreateQueryParametersResolvesOptionalPlaceholderWhenSet() throws 
URISyntaxException {
+        Exchange exchange = new DefaultExchange(camelContext);
+        exchange.getMessage().setHeader("filter", "active");
+
+        String result = RestProducer.createQueryParameters("filter={filter?}", 
exchange);
+
+        assertThat(result).isEqualTo("filter=active");
+    }
+
+    @Test
+    void testCreateQueryParametersHandlesMixedOptionalAndRequired() throws 
URISyntaxException {
+        Exchange exchange = new DefaultExchange(camelContext);
+        exchange.getMessage().setHeader("page", "1");
+        // filter is optional and not set
+
+        String result = 
RestProducer.createQueryParameters("page={page}&filter={filter?}", exchange);
+
+        assertThat(result).isEqualTo("page=1");
+    }
+
+    @Test
+    void testCreateQueryParametersFallsBackToVariable() throws 
URISyntaxException {
+        Exchange exchange = new DefaultExchange(camelContext);
+        exchange.setVariable("orderId", "456");
+
+        String result = RestProducer.createQueryParameters("order={orderId}", 
exchange);
+
+        assertThat(result).isEqualTo("order=456");
+    }
+
+    @Test
+    void testCreateQueryParametersReturnsNullForNullInput() throws 
URISyntaxException {
+        Exchange exchange = new DefaultExchange(camelContext);
+
+        String result = RestProducer.createQueryParameters(null, exchange);
+
+        assertThat(result).isNull();
+    }
+
+    @Test
+    void testCreateQueryParametersHandlesUrlEncodedPlaceholder() throws 
URISyntaxException {
+        Exchange exchange = new DefaultExchange(camelContext);
+        exchange.getMessage().setHeader("name", "John Doe");
+
+        String result = RestProducer.createQueryParameters("name=%7Bname%7D", 
exchange);
+
+        assertThat(result).isEqualTo("name=John+Doe");
+    }
+
+    @Test
+    void testCreateQueryParametersKeepsUnresolvedRequiredPlaceholder() throws 
URISyntaxException {
+        Exchange exchange = new DefaultExchange(camelContext);
+
+        String result = RestProducer.createQueryParameters("id={userId}", 
exchange);
+
+        assertThat(result).isEqualTo("id=%7BuserId%7D");
+    }
+
+    // ==================== PrepareExchange Tests ====================
+
+    @Test
+    void testPrepareExchangeSetsHttpMethodHeader() throws Exception {
+        RestEndpoint endpoint = (RestEndpoint) 
camelContext.getEndpoint("rest:post:users?host=localhost");
+        endpoint.setParameters(new HashMap<>());
+
+        RestConfiguration config = new RestConfiguration();
+        RestProducer producer = new RestProducer(endpoint, mockProducer, 
config);
+
+        Exchange exchange = new DefaultExchange(camelContext);
+        producer.prepareExchange(exchange);
+
+        
assertThat(exchange.getMessage().getHeader(RestConstants.HTTP_METHOD)).isEqualTo("POST");
+    }
+
+    @Test
+    void testPrepareExchangeSetsContentTypeFromProduces() throws Exception {
+        RestEndpoint endpoint = (RestEndpoint) 
camelContext.getEndpoint("rest:post:users?host=localhost");
+        endpoint.setProduces("application/json");
+        endpoint.setParameters(new HashMap<>());
+
+        RestConfiguration config = new RestConfiguration();
+        RestProducer producer = new RestProducer(endpoint, mockProducer, 
config);
+
+        Exchange exchange = new DefaultExchange(camelContext);
+        producer.prepareExchange(exchange);
+
+        
assertThat(exchange.getMessage().getHeader(RestConstants.CONTENT_TYPE)).isEqualTo("application/json");
+    }
+
+    @Test
+    void testPrepareExchangeSetsAcceptFromConsumes() throws Exception {
+        RestEndpoint endpoint = (RestEndpoint) 
camelContext.getEndpoint("rest:get:users?host=localhost");
+        endpoint.setConsumes("application/xml");
+        endpoint.setParameters(new HashMap<>());
+
+        RestConfiguration config = new RestConfiguration();
+        RestProducer producer = new RestProducer(endpoint, mockProducer, 
config);
+
+        Exchange exchange = new DefaultExchange(camelContext);
+        producer.prepareExchange(exchange);
+
+        
assertThat(exchange.getMessage().getHeader(RestConstants.ACCEPT)).isEqualTo("application/xml");
+    }
+
+    @Test
+    void testPrepareExchangeDoesNotOverrideExistingContentType() throws 
Exception {
+        RestEndpoint endpoint = (RestEndpoint) 
camelContext.getEndpoint("rest:post:users?host=localhost");
+        endpoint.setProduces("application/json");
+        endpoint.setParameters(new HashMap<>());
+
+        RestConfiguration config = new RestConfiguration();
+        RestProducer producer = new RestProducer(endpoint, mockProducer, 
config);
+
+        Exchange exchange = new DefaultExchange(camelContext);
+        exchange.getMessage().setHeader(RestConstants.CONTENT_TYPE, 
"text/plain");
+        producer.prepareExchange(exchange);
+
+        
assertThat(exchange.getMessage().getHeader(RestConstants.CONTENT_TYPE)).isEqualTo("text/plain");
+    }
+
+    @Test
+    void testPrepareExchangeDoesNotOverrideExistingAccept() throws Exception {
+        RestEndpoint endpoint = (RestEndpoint) 
camelContext.getEndpoint("rest:get:users?host=localhost");
+        endpoint.setConsumes("application/xml");
+        endpoint.setParameters(new HashMap<>());
+
+        RestConfiguration config = new RestConfiguration();
+        RestProducer producer = new RestProducer(endpoint, mockProducer, 
config);
+
+        Exchange exchange = new DefaultExchange(camelContext);
+        exchange.getMessage().setHeader(RestConstants.ACCEPT, "text/html");
+        producer.prepareExchange(exchange);
+
+        
assertThat(exchange.getMessage().getHeader(RestConstants.ACCEPT)).isEqualTo("text/html");
+    }
+
+    @Test
+    void testPrepareExchangeResolvesUriTemplateFromHeader() throws Exception {
+        RestEndpoint endpoint = (RestEndpoint) 
camelContext.getEndpoint("rest:get:users:{id}?host=http://localhost:8080";);
+        endpoint.setParameters(new HashMap<>());
+
+        RestConfiguration config = new RestConfiguration();
+        RestProducer producer = new RestProducer(endpoint, mockProducer, 
config);
+
+        Exchange exchange = new DefaultExchange(camelContext);
+        exchange.getMessage().setHeader("id", "123");
+        producer.prepareExchange(exchange);
+
+        String uri = 
exchange.getMessage().getHeader(RestConstants.REST_HTTP_URI, String.class);
+        assertThat(uri).isEqualTo("http://localhost:8080/users/123";);
+    }
+
+    @Test
+    void testPrepareExchangeResolvesUriTemplateFromVariable() throws Exception 
{
+        RestEndpoint endpoint = (RestEndpoint) 
camelContext.getEndpoint("rest:get:orders:{orderId}?host=http://localhost";);
+        endpoint.setParameters(new HashMap<>());
+
+        RestConfiguration config = new RestConfiguration();
+        RestProducer producer = new RestProducer(endpoint, mockProducer, 
config);
+
+        Exchange exchange = new DefaultExchange(camelContext);
+        exchange.setVariable("orderId", "789");
+        producer.prepareExchange(exchange);
+
+        String uri = 
exchange.getMessage().getHeader(RestConstants.REST_HTTP_URI, String.class);
+        assertThat(uri).isEqualTo("http://localhost/orders/789";);
+    }
+
+    @Test
+    void testPrepareExchangeSetsQueryParametersHeader() throws Exception {
+        RestEndpoint endpoint = (RestEndpoint) 
camelContext.getEndpoint("rest:get:users?host=localhost");
+        endpoint.setQueryParameters("page=1&size=10");
+        endpoint.setParameters(new HashMap<>());
+
+        RestConfiguration config = new RestConfiguration();
+        RestProducer producer = new RestProducer(endpoint, mockProducer, 
config);
+
+        Exchange exchange = new DefaultExchange(camelContext);
+        producer.prepareExchange(exchange);
+
+        String query = 
exchange.getMessage().getHeader(RestConstants.REST_HTTP_QUERY, String.class);
+        assertThat(query).isEqualTo("page=1&size=10");
+    }
+
+    @Test
+    void testPrepareExchangeResolvesDynamicQueryParameters() throws Exception {
+        RestEndpoint endpoint = (RestEndpoint) 
camelContext.getEndpoint("rest:get:users?host=localhost");
+        endpoint.setQueryParameters("userId={id}");
+        endpoint.setParameters(new HashMap<>());
+
+        RestConfiguration config = new RestConfiguration();
+        RestProducer producer = new RestProducer(endpoint, mockProducer, 
config);
+
+        Exchange exchange = new DefaultExchange(camelContext);
+        exchange.getMessage().setHeader("id", "456");
+        producer.prepareExchange(exchange);
+
+        String query = 
exchange.getMessage().getHeader(RestConstants.REST_HTTP_QUERY, String.class);
+        assertThat(query).isEqualTo("userId=456");
+    }
+
+    @Test
+    void testPrepareExchangeRemovesHttpPathWhenUriIsSet() throws Exception {
+        RestEndpoint endpoint = (RestEndpoint) 
camelContext.getEndpoint("rest:get:users:{id}?host=http://localhost";);
+        endpoint.setParameters(new HashMap<>());
+
+        RestConfiguration config = new RestConfiguration();
+        RestProducer producer = new RestProducer(endpoint, mockProducer, 
config);
+
+        Exchange exchange = new DefaultExchange(camelContext);
+        exchange.getMessage().setHeader("id", "123");
+        exchange.getMessage().setHeader(Exchange.HTTP_PATH, "/old/path");
+        producer.prepareExchange(exchange);
+
+        
assertThat(exchange.getMessage().getHeader(Exchange.HTTP_PATH)).isNull();
+    }
+
+    @Test
+    void testPrepareExchangeCombinesPathAndUriTemplate() throws Exception {
+        RestEndpoint endpoint = (RestEndpoint) 
camelContext.getEndpoint("rest:get:api:users/{id}?host=http://localhost";);
+        endpoint.setParameters(new HashMap<>());
+
+        RestConfiguration config = new RestConfiguration();
+        RestProducer producer = new RestProducer(endpoint, mockProducer, 
config);
+
+        Exchange exchange = new DefaultExchange(camelContext);
+        exchange.getMessage().setHeader("id", "999");
+        producer.prepareExchange(exchange);
+
+        String uri = 
exchange.getMessage().getHeader(RestConstants.REST_HTTP_URI, String.class);
+        assertThat(uri).isEqualTo("http://localhost/api/users/999";);
+    }
+
+    @Test
+    void 
testPrepareExchangeDoesNotResolveTemplateWhenPrepareUriTemplateIsFalse() throws 
Exception {
+        RestEndpoint endpoint = (RestEndpoint) 
camelContext.getEndpoint("rest:get:users:{id}?host=http://localhost";);
+        endpoint.setParameters(new HashMap<>());
+
+        RestConfiguration config = new RestConfiguration();
+        RestProducer producer = new RestProducer(endpoint, mockProducer, 
config);
+        producer.setPrepareUriTemplate(false);
+
+        Exchange exchange = new DefaultExchange(camelContext);
+        exchange.getMessage().setHeader("id", "123");
+        producer.prepareExchange(exchange);
+
+        String uri = 
exchange.getMessage().getHeader(RestConstants.REST_HTTP_URI, String.class);
+        assertThat(uri).isNull();
+    }
+}
diff --git 
a/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestRegistryStatefulTest.java
 
b/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestRegistryStatefulTest.java
new file mode 100644
index 000000000000..4a7628ba52fb
--- /dev/null
+++ 
b/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestRegistryStatefulTest.java
@@ -0,0 +1,148 @@
+/*
+ * 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.rest;
+
+import java.util.List;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Endpoint;
+import org.apache.camel.Processor;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.spi.RestRegistry;
+import org.apache.camel.support.DefaultConsumer;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+class RestRegistryStatefulTest {
+
+    private DefaultRestRegistry registry;
+    private CamelContext camelContext;
+
+    @Mock
+    private Endpoint mockEndpoint;
+
+    @Mock
+    private Processor mockProcessor;
+
+    @BeforeEach
+    void setUp() throws Exception {
+        camelContext = new DefaultCamelContext();
+        camelContext.start();
+
+        // Configure mock endpoint to return the CamelContext
+        when(mockEndpoint.getCamelContext()).thenReturn(camelContext);
+
+        registry = new DefaultRestRegistry();
+        registry.setCamelContext(camelContext);
+        registry.start();
+    }
+
+    @AfterEach
+    void tearDown() throws Exception {
+        registry.stop();
+        camelContext.stop();
+    }
+
+    @Test
+    void testServiceStateWithStatefulConsumer() throws Exception {
+        // Create a real DefaultConsumer which is a StatefulService
+        TestConsumer consumer = new TestConsumer(mockEndpoint, mockProcessor);
+
+        registry.addRestService(consumer, false, 
"http://localhost:8080/api/users";,
+                "http://localhost:8080";, "/api", "/users", "GET",
+                "application/json", "application/json", null, null,
+                "route1", "Get users");
+
+        List<RestRegistry.RestService> services = 
registry.listAllRestServices();
+        assertThat(services).hasSize(1);
+
+        // Before starting, status should be Stopped
+        assertThat(services.get(0).getState()).isEqualTo("Stopped");
+
+        // Start the consumer
+        consumer.start();
+
+        // After starting, status should be Started
+        assertThat(services.get(0).getState()).isEqualTo("Started");
+
+        // Stop the consumer
+        consumer.stop();
+
+        // After stopping, status should be Stopped
+        assertThat(services.get(0).getState()).isEqualTo("Stopped");
+    }
+
+    @Test
+    void testServiceStateWithStartedStatefulConsumer() throws Exception {
+        TestConsumer consumer = new TestConsumer(mockEndpoint, mockProcessor);
+        consumer.start();
+
+        registry.addRestService(consumer, false, 
"http://localhost:8080/api/orders";,
+                "http://localhost:8080";, "/api", "/orders", "POST",
+                null, null, null, null, "route2", "Create order");
+
+        List<RestRegistry.RestService> services = 
registry.listAllRestServices();
+        assertThat(services.get(0).getState()).isEqualTo("Started");
+
+        consumer.stop();
+    }
+
+    @Test
+    void testServiceStateWithSuspendedConsumer() throws Exception {
+        TestConsumer consumer = new TestConsumer(mockEndpoint, mockProcessor);
+        consumer.start();
+        consumer.suspend();
+
+        registry.addRestService(consumer, false, 
"http://localhost:8080/api/items";,
+                "http://localhost:8080";, "/api", "/items", "DELETE",
+                null, null, null, null, "route3", "Delete item");
+
+        List<RestRegistry.RestService> services = 
registry.listAllRestServices();
+        assertThat(services.get(0).getState()).isEqualTo("Suspended");
+
+        consumer.resume();
+        consumer.stop();
+    }
+
+    /**
+     * Test consumer that extends DefaultConsumer for testing stateful service 
behavior
+     */
+    private static class TestConsumer extends DefaultConsumer {
+
+        public TestConsumer(Endpoint endpoint, Processor processor) {
+            super(endpoint, processor);
+        }
+
+        @Override
+        protected void doStart() throws Exception {
+            // No-op for test
+        }
+
+        @Override
+        protected void doStop() throws Exception {
+            // No-op for test
+        }
+    }
+}

Reply via email to