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

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


The following commit(s) were added to refs/heads/main by this push:
     new f94a58d05e10 CAMEL-25769: camel-core - the REST binding answers an 
object as JSON when the request had a Content-Type the binding does not write 
(#28101)
f94a58d05e10 is described below

commit f94a58d05e1066c4427c34e258fca4f995867744
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Oct 19 21:24:28 2026 +0200

    CAMEL-25769: camel-core - the REST binding answers an object as JSON when 
the request had a Content-Type the binding does not write (#28101)
    
    Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
---
 ...tformHttpRestBindingRequestContentTypeTest.java | 102 +++++++++++++++++++++
 .../camel/support/processor/RestBindingAdvice.java |  36 ++++++++
 2 files changed, 138 insertions(+)

diff --git 
a/components/camel-platform-http-vertx/src/test/java/org/apache/camel/component/platform/http/vertx/VertxPlatformHttpRestBindingRequestContentTypeTest.java
 
b/components/camel-platform-http-vertx/src/test/java/org/apache/camel/component/platform/http/vertx/VertxPlatformHttpRestBindingRequestContentTypeTest.java
new file mode 100644
index 000000000000..9a1c23ebc0fe
--- /dev/null
+++ 
b/components/camel-platform-http-vertx/src/test/java/org/apache/camel/component/platform/http/vertx/VertxPlatformHttpRestBindingRequestContentTypeTest.java
@@ -0,0 +1,102 @@
+/*
+ * 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.platform.http.vertx;
+
+import java.util.Map;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Exchange;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.model.rest.RestBindingMode;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static io.restassured.RestAssured.given;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.equalTo;
+
+/**
+ * CAMEL-25769: the Content-Type of a request describes its body, not the 
answer. Under json binding, a GET sent with
+ * Content-Type text/plain gets its object answer as JSON, as a GET without 
one does.
+ */
+public class VertxPlatformHttpRestBindingRequestContentTypeTest {
+
+    private CamelContext context;
+
+    @BeforeEach
+    public void startCamel() throws Exception {
+        context = VertxPlatformHttpEngineTest.createCamelContext();
+        context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() {
+                restConfiguration().bindingMode(RestBindingMode.json);
+                rest("/orders")
+                        .get("/map").to("direct:map")
+                        .get("/text").to("direct:text")
+                        .get("/csv").to("direct:csv");
+                from("direct:map").setBody(constant(Map.of("orderId", 
"ORD-1002", "customer", "C-207")));
+                from("direct:text").setBody(constant("plain answer"));
+                from("direct:csv").setHeader(Exchange.CONTENT_TYPE, 
constant("text/csv")).setBody(constant("ORD-1002;C-207"));
+            }
+        });
+        VertxPlatformHttpEngineTest.startCamelContext(context);
+    }
+
+    @AfterEach
+    public void stopCamel() {
+        context.stop();
+    }
+
+    @Test
+    public void testAnObjectAnswerIsJsonWhateverTheContentTypeOfTheRequest() {
+        // was a 500: the Map could not be written as text/plain
+        given().contentType("text/plain;charset=UTF-8")
+                .when().get("/orders/map")
+                .then().statusCode(200)
+                .header("Content-Type", containsString("application/json"))
+                .body("orderId", equalTo("ORD-1002"));
+        given().contentType("application/json")
+                .when().get("/orders/map")
+                .then().statusCode(200)
+                .header("Content-Type", containsString("application/json"))
+                .body("orderId", equalTo("ORD-1002"));
+        given()
+                .when().get("/orders/map")
+                .then().statusCode(200)
+                .header("Content-Type", containsString("application/json"))
+                .body("orderId", equalTo("ORD-1002"));
+    }
+
+    @Test
+    public void testATextAnswerToATextRequestStaysText() {
+        given().contentType("text/plain;charset=UTF-8")
+                .when().get("/orders/text")
+                .then().statusCode(200)
+                .header("Content-Type", containsString("text/plain"))
+                .body(equalTo("plain answer"));
+    }
+
+    @Test
+    public void testTheContentTypeTheRouteSetsWins() {
+        given().contentType("text/plain;charset=UTF-8")
+                .when().get("/orders/csv")
+                .then().statusCode(200)
+                .header("Content-Type", containsString("text/csv"))
+                .body(equalTo("ORD-1002;C-207"));
+    }
+}
diff --git 
a/core/camel-support/src/main/java/org/apache/camel/support/processor/RestBindingAdvice.java
 
b/core/camel-support/src/main/java/org/apache/camel/support/processor/RestBindingAdvice.java
index 7371f6832412..3edb96b6fc33 100644
--- 
a/core/camel-support/src/main/java/org/apache/camel/support/processor/RestBindingAdvice.java
+++ 
b/core/camel-support/src/main/java/org/apache/camel/support/processor/RestBindingAdvice.java
@@ -16,6 +16,9 @@
  */
 package org.apache.camel.support.processor;
 
+import java.io.InputStream;
+import java.io.Reader;
+import java.nio.ByteBuffer;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Locale;
@@ -27,6 +30,7 @@ import org.apache.camel.CamelContext;
 import org.apache.camel.CamelExchangeException;
 import org.apache.camel.Exchange;
 import org.apache.camel.Message;
+import org.apache.camel.StreamCache;
 import org.apache.camel.spi.CamelInternalProcessorAdvice;
 import org.apache.camel.spi.DataFormat;
 import org.apache.camel.spi.DataType;
@@ -58,6 +62,7 @@ public class RestBindingAdvice extends ServiceSupport 
implements CamelInternalPr
 
     private static final String STATE_KEY_DO_MARSHAL = "doMarshal";
     private static final String STATE_KEY_ACCEPT = "accept";
+    private static final String STATE_KEY_REQUEST_CONTENT_TYPE = 
"requestContentType";
     private static final String STATE_JSON = "json";
     private static final String STATE_XML = "xml";
 
@@ -196,6 +201,8 @@ public class RestBindingAdvice extends ServiceSupport 
implements CamelInternalPr
         boolean isJson = false;
 
         String contentType = ExchangeHelper.getContentType(exchange);
+        // the answer is bound with the Content-Type of the binding when the 
route leaves this one (CAMEL-25769)
+        state.put(STATE_KEY_REQUEST_CONTENT_TYPE, contentType);
         if (contentType != null) {
             isXml = contentType.toLowerCase(Locale.ENGLISH).contains("xml");
             isJson = contentType.toLowerCase(Locale.ENGLISH).contains("json");
@@ -415,6 +422,9 @@ public class RestBindingAdvice extends ServiceSupport 
implements CamelInternalPr
             return;
         }
 
+        // the Content-Type of the request describes its body, not the answer 
(a GET has no body)
+        dropRequestContentType(exchange, state, isXml, isJson);
+
         // ensure there is a content type header (even if binding is off)
         ensureHeaderContentType(produces, isXml, isJson, exchange);
 
@@ -514,6 +524,32 @@ public class RestBindingAdvice extends ServiceSupport 
implements CamelInternalPr
         }
     }
 
+    /**
+     * Removes the Content-Type of the request when the route did not set 
another, the binding does not write that type,
+     * and the answer is an object (a Map, a List, a POJO): the binding then 
sets the Content-Type of its mode and
+     * marshals the answer, as for a request without a Content-Type. Before, a 
GET sent with Content-Type text/plain got
+     * a 500, as the object could not be written as text/plain (CAMEL-25769). 
A request type the binding writes (json
+     * for a json binding) is kept as before, and so is a body that is already 
text, bytes or a stream, which the route
+     * may answer as such.
+     */
+    private static void dropRequestContentType(
+            Exchange exchange, Map<String, Object> state, boolean isXml, 
boolean isJson) {
+        Object requestContentType = state.get(STATE_KEY_REQUEST_CONTENT_TYPE);
+        if (requestContentType == null || 
!requestContentType.equals(ExchangeHelper.getContentType(exchange))) {
+            return;
+        }
+        String type = 
requestContentType.toString().toLowerCase(Locale.ENGLISH);
+        if (isJson && type.contains("json") || isXml && type.contains("xml")) {
+            return;
+        }
+        Object body = exchange.getMessage().getBody();
+        if (body instanceof CharSequence || body instanceof byte[] || body 
instanceof InputStream
+                || body instanceof Reader || body instanceof StreamCache || 
body instanceof ByteBuffer) {
+            return;
+        }
+        exchange.getMessage().removeHeader(Exchange.CONTENT_TYPE);
+    }
+
     private void ensureHeaderContentType(String contentType, boolean isXml, 
boolean isJson, Exchange exchange) {
         String type = ExchangeHelper.getContentType(exchange);
         if (type != null) {

Reply via email to