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 cf6980d06db0 CAMEL-26143: camel-cxf - fix byte[] reply, POJO parameter 
splitting and bodies sent as empty messages (#28920)
cf6980d06db0 is described below

commit cf6980d06db0fe8fae14b67c16f9ca8e32c98c0d
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Nov 2 20:46:14 2026 +0100

    CAMEL-26143: camel-cxf - fix byte[] reply, POJO parameter splitting and 
bodies sent as empty messages (#28920)
    
    …nd bodies sent as empty messages
    
    A byte[] reply in POJO and CXF_MESSAGE mode was cast to Object[] and gave a
    ClassCastException SOAP fault; only an Object[] is now taken as the list of 
values.
    The POJO producer split a body that is not a List or an array like the 
Split EIP
    (a String with a comma became several parameters, a byte[] one per byte); 
now only
    a Collection or an Object[] holds the parameters, any other body is the one 
parameter.
    In CXF_MESSAGE mode a body that is not a SOAPMessage or a Source was sent 
as an empty
    envelope; it is now converted to a DOMSource, and fails when it cannot be 
converted.
    PAYLOAD and RAW consumer replies that do not convert are now a SOAP fault 
instead of
    an empty response.
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../org/apache/camel/catalog/components/cxf.json   |   4 +-
 .../apache/camel/catalog/docs/cxf-component.adoc   |   7 +
 .../org/apache/camel/component/cxf/jaxws/cxf.json  |   4 +-
 .../src/main/docs/cxf-component.adoc               |   7 +
 .../camel/component/cxf/jaxws/CxfEndpoint.java     |  37 +++--
 .../camel/component/cxf/jaxws/CxfProducer.java     |  25 +--
 .../component/cxf/jaxws/DefaultCxfBinding.java     |  43 ++++-
 .../camel/component/cxf/jaxws/BodyTypeService.java |  25 +++
 .../component/cxf/jaxws/CxfBodyTypesTest.java      | 174 +++++++++++++++++++++
 .../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc    |  17 ++
 10 files changed, 300 insertions(+), 43 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/cxf.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/cxf.json
index d757fadf9c2a..ebf7b925a243 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/cxf.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/cxf.json
@@ -43,8 +43,8 @@
   },
   "bodyTypes": {
     "consumerOut": { "carriers": [ "list", "null" ], "byOption": [ { "option": 
"dataFormat", "value": "PAYLOAD", "carriers": [ "pojo" ], "javaType": 
"org.apache.camel.component.cxf.common.CxfPayload" }, { "option": "dataFormat", 
"value": "RAW", "carriers": [ "stream" ] }, { "option": "dataFormat", "value": 
"MESSAGE", "carriers": [ "stream" ] }, { "option": "dataFormat", "value": 
"CXF_MESSAGE", "carriers": [ "pojo" ], "javaType": 
"jakarta.xml.soap.SOAPMessage" } ], "description": "POJO: a [...]
-    "consumerReply": { "carriers": [ "text", "value", "pojo", "list", "null" 
], "byOption": [ { "option": "dataFormat", "value": "PAYLOAD", "carriers": [ 
"pojo", "text", "bytes", "stream", "null" ] }, { "option": "dataFormat", 
"value": "RAW", "carriers": [ "text", "bytes", "stream", "file", "null" ] }, { 
"option": "dataFormat", "value": "MESSAGE", "carriers": [ "text", "bytes", 
"stream", "file", "null" ] }, { "option": "dataFormat", "value": "CXF_MESSAGE", 
"carriers": [ "pojo" ] } ], "de [...]
-    "producerIn": { "carriers": [ "list", "text", "value", "pojo", "null" ], 
"byOption": [ { "option": "dataFormat", "value": "PAYLOAD", "carriers": [ 
"pojo", "text", "bytes", "stream", "file" ] }, { "option": "dataFormat", 
"value": "RAW", "carriers": [ "text", "bytes", "stream", "file" ] }, { 
"option": "dataFormat", "value": "MESSAGE", "carriers": [ "text", "bytes", 
"stream", "file" ] }, { "option": "dataFormat", "value": "CXF_MESSAGE", 
"carriers": [ "pojo" ] } ], "description": "POJO:  [...]
+    "consumerReply": { "carriers": [ "text", "value", "pojo", "list", "null" 
], "byOption": [ { "option": "dataFormat", "value": "PAYLOAD", "carriers": [ 
"pojo", "text", "bytes", "stream", "null" ] }, { "option": "dataFormat", 
"value": "RAW", "carriers": [ "text", "bytes", "stream", "file", "null" ] }, { 
"option": "dataFormat", "value": "MESSAGE", "carriers": [ "text", "bytes", 
"stream", "file", "null" ] }, { "option": "dataFormat", "value": "CXF_MESSAGE", 
"carriers": [ "pojo", "text", " [...]
+    "producerIn": { "carriers": [ "list", "text", "value", "pojo", "null" ], 
"byOption": [ { "option": "dataFormat", "value": "PAYLOAD", "carriers": [ 
"pojo", "text", "bytes", "stream", "file" ] }, { "option": "dataFormat", 
"value": "RAW", "carriers": [ "text", "bytes", "stream", "file" ] }, { 
"option": "dataFormat", "value": "MESSAGE", "carriers": [ "text", "bytes", 
"stream", "file" ] }, { "option": "dataFormat", "value": "CXF_MESSAGE", 
"carriers": [ "pojo", "text", "bytes", "stream" ]  [...]
     "producerOut": { "carriers": [ "list", "null", "unchanged" ], "byOption": 
[ { "option": "dataFormat", "value": "PAYLOAD", "carriers": [ "pojo" ], 
"javaType": "org.apache.camel.component.cxf.common.CxfPayload" }, { "option": 
"dataFormat", "value": "RAW", "carriers": [ "stream" ] }, { "option": 
"dataFormat", "value": "MESSAGE", "carriers": [ "stream" ] }, { "option": 
"dataFormat", "value": "CXF_MESSAGE", "carriers": [ "pojo" ], "javaType": 
"jakarta.xml.soap.SOAPMessage" } ], "descripti [...]
   },
   "properties": {
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/cxf-component.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/cxf-component.adoc
index 747329da3c22..b918eaed603f 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/cxf-component.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/cxf-component.adoc
@@ -134,6 +134,10 @@ If you require just the SOAP XML as a String, you can set 
the message body
 with `message.getSOAPPart()`, and the Camel converter can do the rest of the 
work
 for you.
 
+The message body that a producer sends, and the reply of a consumer, is a 
`SOAPMessage` or a `javax.xml.transform.Source`
+of the whole SOAP envelope. Any other body, such as the envelope as a 
`String`, `byte[]`, `InputStream` or DOM
+`Document`, is converted to a `DOMSource`; a body that cannot be converted 
fails the exchange (a SOAP fault for a consumer).
+
 === Streaming Support in PAYLOAD mode
 
 The Camel CXF component now supports streaming of incoming
@@ -650,6 +654,9 @@ header, then add the method parameters to a list, and 
initialize the
 message with this parameter list. The response message's body is a
 messageContentsList, you can get the result from that list.
 
+A `List` (or another `Collection`) or an `Object[]` body holds the parameters. 
Any other body, such as a `String`
+or a `byte[]`, is the one parameter of the operation: a `String` with a comma 
is not split into several parameters.
+
 If you don't specify the operation name in the message header,
 `CxfProducer` will try to use the `defaultOperationName` from
 `CxfEndpoint`, if there is no `defaultOperationName` set on
diff --git 
a/components/camel-cxf/camel-cxf-soap/src/generated/resources/META-INF/org/apache/camel/component/cxf/jaxws/cxf.json
 
b/components/camel-cxf/camel-cxf-soap/src/generated/resources/META-INF/org/apache/camel/component/cxf/jaxws/cxf.json
index d757fadf9c2a..ebf7b925a243 100644
--- 
a/components/camel-cxf/camel-cxf-soap/src/generated/resources/META-INF/org/apache/camel/component/cxf/jaxws/cxf.json
+++ 
b/components/camel-cxf/camel-cxf-soap/src/generated/resources/META-INF/org/apache/camel/component/cxf/jaxws/cxf.json
@@ -43,8 +43,8 @@
   },
   "bodyTypes": {
     "consumerOut": { "carriers": [ "list", "null" ], "byOption": [ { "option": 
"dataFormat", "value": "PAYLOAD", "carriers": [ "pojo" ], "javaType": 
"org.apache.camel.component.cxf.common.CxfPayload" }, { "option": "dataFormat", 
"value": "RAW", "carriers": [ "stream" ] }, { "option": "dataFormat", "value": 
"MESSAGE", "carriers": [ "stream" ] }, { "option": "dataFormat", "value": 
"CXF_MESSAGE", "carriers": [ "pojo" ], "javaType": 
"jakarta.xml.soap.SOAPMessage" } ], "description": "POJO: a [...]
-    "consumerReply": { "carriers": [ "text", "value", "pojo", "list", "null" 
], "byOption": [ { "option": "dataFormat", "value": "PAYLOAD", "carriers": [ 
"pojo", "text", "bytes", "stream", "null" ] }, { "option": "dataFormat", 
"value": "RAW", "carriers": [ "text", "bytes", "stream", "file", "null" ] }, { 
"option": "dataFormat", "value": "MESSAGE", "carriers": [ "text", "bytes", 
"stream", "file", "null" ] }, { "option": "dataFormat", "value": "CXF_MESSAGE", 
"carriers": [ "pojo" ] } ], "de [...]
-    "producerIn": { "carriers": [ "list", "text", "value", "pojo", "null" ], 
"byOption": [ { "option": "dataFormat", "value": "PAYLOAD", "carriers": [ 
"pojo", "text", "bytes", "stream", "file" ] }, { "option": "dataFormat", 
"value": "RAW", "carriers": [ "text", "bytes", "stream", "file" ] }, { 
"option": "dataFormat", "value": "MESSAGE", "carriers": [ "text", "bytes", 
"stream", "file" ] }, { "option": "dataFormat", "value": "CXF_MESSAGE", 
"carriers": [ "pojo" ] } ], "description": "POJO:  [...]
+    "consumerReply": { "carriers": [ "text", "value", "pojo", "list", "null" 
], "byOption": [ { "option": "dataFormat", "value": "PAYLOAD", "carriers": [ 
"pojo", "text", "bytes", "stream", "null" ] }, { "option": "dataFormat", 
"value": "RAW", "carriers": [ "text", "bytes", "stream", "file", "null" ] }, { 
"option": "dataFormat", "value": "MESSAGE", "carriers": [ "text", "bytes", 
"stream", "file", "null" ] }, { "option": "dataFormat", "value": "CXF_MESSAGE", 
"carriers": [ "pojo", "text", " [...]
+    "producerIn": { "carriers": [ "list", "text", "value", "pojo", "null" ], 
"byOption": [ { "option": "dataFormat", "value": "PAYLOAD", "carriers": [ 
"pojo", "text", "bytes", "stream", "file" ] }, { "option": "dataFormat", 
"value": "RAW", "carriers": [ "text", "bytes", "stream", "file" ] }, { 
"option": "dataFormat", "value": "MESSAGE", "carriers": [ "text", "bytes", 
"stream", "file" ] }, { "option": "dataFormat", "value": "CXF_MESSAGE", 
"carriers": [ "pojo", "text", "bytes", "stream" ]  [...]
     "producerOut": { "carriers": [ "list", "null", "unchanged" ], "byOption": 
[ { "option": "dataFormat", "value": "PAYLOAD", "carriers": [ "pojo" ], 
"javaType": "org.apache.camel.component.cxf.common.CxfPayload" }, { "option": 
"dataFormat", "value": "RAW", "carriers": [ "stream" ] }, { "option": 
"dataFormat", "value": "MESSAGE", "carriers": [ "stream" ] }, { "option": 
"dataFormat", "value": "CXF_MESSAGE", "carriers": [ "pojo" ], "javaType": 
"jakarta.xml.soap.SOAPMessage" } ], "descripti [...]
   },
   "properties": {
diff --git 
a/components/camel-cxf/camel-cxf-soap/src/main/docs/cxf-component.adoc 
b/components/camel-cxf/camel-cxf-soap/src/main/docs/cxf-component.adoc
index 747329da3c22..b918eaed603f 100644
--- a/components/camel-cxf/camel-cxf-soap/src/main/docs/cxf-component.adoc
+++ b/components/camel-cxf/camel-cxf-soap/src/main/docs/cxf-component.adoc
@@ -134,6 +134,10 @@ If you require just the SOAP XML as a String, you can set 
the message body
 with `message.getSOAPPart()`, and the Camel converter can do the rest of the 
work
 for you.
 
+The message body that a producer sends, and the reply of a consumer, is a 
`SOAPMessage` or a `javax.xml.transform.Source`
+of the whole SOAP envelope. Any other body, such as the envelope as a 
`String`, `byte[]`, `InputStream` or DOM
+`Document`, is converted to a `DOMSource`; a body that cannot be converted 
fails the exchange (a SOAP fault for a consumer).
+
 === Streaming Support in PAYLOAD mode
 
 The Camel CXF component now supports streaming of incoming
@@ -650,6 +654,9 @@ header, then add the method parameters to a list, and 
initialize the
 message with this parameter list. The response message's body is a
 messageContentsList, you can get the result from that list.
 
+A `List` (or another `Collection`) or an `Object[]` body holds the parameters. 
Any other body, such as a `String`
+or a `byte[]`, is the one parameter of the operation: a `String` with a comma 
is not split into several parameters.
+
 If you don't specify the operation name in the message header,
 `CxfProducer` will try to use the `defaultOperationName` from
 `CxfEndpoint`, if there is no `defaultOperationName` set on
diff --git 
a/components/camel-cxf/camel-cxf-soap/src/main/java/org/apache/camel/component/cxf/jaxws/CxfEndpoint.java
 
b/components/camel-cxf/camel-cxf-soap/src/main/java/org/apache/camel/component/cxf/jaxws/CxfEndpoint.java
index 184931119705..518b0093f2e8 100644
--- 
a/components/camel-cxf/camel-cxf-soap/src/main/java/org/apache/camel/component/cxf/jaxws/CxfEndpoint.java
+++ 
b/components/camel-cxf/camel-cxf-soap/src/main/java/org/apache/camel/component/cxf/jaxws/CxfEndpoint.java
@@ -164,15 +164,18 @@ import static 
org.apache.camel.component.cxf.common.message.CxfConstants.SCHEME_
                                                                     
Carrier.TEXT, Carrier.BYTES, Carrier.STREAM,
                                                                     
Carrier.FILE, Carrier.NULL }),
                                              @BodyType.When(option = 
"dataFormat", value = "CXF_MESSAGE",
-                                                            carriers = 
Carrier.POJO) },
+                                                            carriers = {
+                                                                    
Carrier.POJO, Carrier.TEXT, Carrier.BYTES,
+                                                                    
Carrier.STREAM }) },
                                      description = "POJO: the return value of 
the operation, of the type the service class"
-                                                   + " declares, or a List or 
array of it and the out and holder parameters;"
-                                                   + " another type is a SOAP 
fault, so the request MessageContentsList only"
-                                                   + " fits an echo. PAYLOAD: 
a CxfPayload, or the XML of the soap:Body"
-                                                   + " contents as text, 
bytes, a stream or a DOM; a body that does not convert"
-                                                   + " sends an empty 
soap:Body. RAW: the whole SOAP envelope as text, bytes, a"
-                                                   + " stream or a file. 
CXF_MESSAGE: a SOAPMessage or a javax.xml.transform"
-                                                   + " Source; text or a 
stream sends an empty envelope"),
+                                                   + " declares (a byte array 
is one value), or a List or an Object array of it"
+                                                   + " and the out and holder 
parameters; another type is a SOAP fault, so the"
+                                                   + " request 
MessageContentsList only fits an echo. PAYLOAD: a CxfPayload, or"
+                                                   + " the XML of the 
soap:Body contents as text, bytes, a stream or a DOM. RAW:"
+                                                   + " the whole SOAP envelope 
as text, bytes, a stream or a file. CXF_MESSAGE: a"
+                                                   + " SOAPMessage, a 
javax.xml.transform Source, or the whole SOAP envelope as"
+                                                   + " text, bytes, a stream 
or a DOM. A body that does not convert is a SOAP"
+                                                   + " fault"),
            producerIn = @BodyType(value = { Carrier.LIST, Carrier.TEXT, 
Carrier.VALUE, Carrier.POJO, Carrier.NULL },
                                   byOption = {
                                           @BodyType.When(option = 
"dataFormat", value = "PAYLOAD",
@@ -188,15 +191,17 @@ import static 
org.apache.camel.component.cxf.common.message.CxfConstants.SCHEME_
                                                                  Carrier.TEXT, 
Carrier.BYTES, Carrier.STREAM,
                                                                  Carrier.FILE 
}),
                                           @BodyType.When(option = 
"dataFormat", value = "CXF_MESSAGE",
-                                                         carriers = 
Carrier.POJO) },
+                                                         carriers = {
+                                                                 Carrier.POJO, 
Carrier.TEXT, Carrier.BYTES,
+                                                                 
Carrier.STREAM }) },
                                   description = "POJO: the parameters of the 
operation (the CamelCxfOperationName header or"
-                                                + " defaultOperationName) as a 
List or an array, or the one parameter as"
-                                                + " the body; each of the type 
the service class declares. A body that is"
-                                                + " not a List is split like 
the split EIP does, so text with a comma or a"
-                                                + " byte array is several 
parameters: wrap it in a List. PAYLOAD: a"
-                                                + " CxfPayload, or the XML of 
the soap:Body contents as text, bytes, a stream,"
-                                                + " a file or a DOM. RAW: the 
whole SOAP envelope. CXF_MESSAGE: a SOAPMessage"
-                                                + " or a javax.xml.transform 
Source"),
+                                                + " defaultOperationName) as a 
List (or another Collection) or an Object array,"
+                                                + " or the one parameter as 
the body; each of the type the service class"
+                                                + " declares. Any other body, 
such as text or a byte array, is the one"
+                                                + " parameter. PAYLOAD: a 
CxfPayload, or the XML of the soap:Body contents as"
+                                                + " text, bytes, a stream, a 
file or a DOM. RAW: the whole SOAP envelope."
+                                                + " CXF_MESSAGE: a 
SOAPMessage, a javax.xml.transform Source, or the whole SOAP"
+                                                + " envelope as text, bytes, a 
stream or a DOM; another body fails"),
            producerOut = @BodyType(value = { Carrier.LIST, Carrier.NULL, 
Carrier.UNCHANGED },
                                    byOption = {
                                            @BodyType.When(option = 
"dataFormat", value = "PAYLOAD", carriers = Carrier.POJO,
diff --git 
a/components/camel-cxf/camel-cxf-soap/src/main/java/org/apache/camel/component/cxf/jaxws/CxfProducer.java
 
b/components/camel-cxf/camel-cxf-soap/src/main/java/org/apache/camel/component/cxf/jaxws/CxfProducer.java
index ed086aa97fee..2d8277bc568e 100644
--- 
a/components/camel-cxf/camel-cxf-soap/src/main/java/org/apache/camel/component/cxf/jaxws/CxfProducer.java
+++ 
b/components/camel-cxf/camel-cxf-soap/src/main/java/org/apache/camel/component/cxf/jaxws/CxfProducer.java
@@ -358,24 +358,13 @@ public class CxfProducer extends DefaultAsyncProducer {
             }
             if (body instanceof Object[] objectArray) {
                 params = objectArray;
-            } else if (body instanceof List) {
-                // Now we just check if the request is List
-                params = ((List<?>) body).toArray();
+            } else if (body instanceof Collection<?> collection) {
+                // a List (or another collection) holds the parameters
+                params = collection.toArray();
             } else {
-                // maybe we can iterate the body and that way create a list 
for the parameters
-                // then end users do not need to trouble with List
-                Iterator<?> it = exchange.getIn().getBody(Iterator.class);
-                if (it != null && it.hasNext()) {
-                    List<?> list = 
exchange.getContext().getTypeConverter().convertTo(List.class, it);
-                    if (list != null) {
-                        params = list.toArray();
-                    }
-                }
-                if (params == null) {
-                    // now we just use the body as single parameter
-                    params = new Object[1];
-                    params[0] = exchange.getIn().getBody();
-                }
+                // any other body (such as a String, a byte[] or a POJO) is 
the one parameter,
+                // and is not split into several parameters
+                params = new Object[] { body };
             }
             // make sure we have the right number of parameters
             checkParameterSize(endpoint, exchange, params);
@@ -388,7 +377,7 @@ public class CxfProducer extends DefaultAsyncProducer {
             params[0] = exchange.getIn().getMandatoryBody(InputStream.class);
         } else if (endpoint.getDataFormat().dealias() == 
DataFormat.CXF_MESSAGE) {
             params = new Object[1];
-            params[0] = exchange.getIn().getBody();
+            params[0] = DefaultCxfBinding.toCxfMessageBody(exchange.getIn());
         }
 
         if (LOG.isTraceEnabled()) {
diff --git 
a/components/camel-cxf/camel-cxf-soap/src/main/java/org/apache/camel/component/cxf/jaxws/DefaultCxfBinding.java
 
b/components/camel-cxf/camel-cxf-soap/src/main/java/org/apache/camel/component/cxf/jaxws/DefaultCxfBinding.java
index ee13313ca47f..b125512857a7 100644
--- 
a/components/camel-cxf/camel-cxf-soap/src/main/java/org/apache/camel/component/cxf/jaxws/DefaultCxfBinding.java
+++ 
b/components/camel-cxf/camel-cxf-soap/src/main/java/org/apache/camel/component/cxf/jaxws/DefaultCxfBinding.java
@@ -37,6 +37,7 @@ import java.util.StringJoiner;
 import java.util.TreeMap;
 
 import jakarta.activation.DataHandler;
+import jakarta.xml.soap.SOAPMessage;
 import jakarta.xml.ws.Holder;
 
 import javax.security.auth.Subject;
@@ -53,6 +54,7 @@ import org.w3c.dom.Node;
 import org.apache.camel.Exchange;
 import org.apache.camel.ExchangePattern;
 import org.apache.camel.ExchangePropertyKey;
+import org.apache.camel.InvalidPayloadRuntimeException;
 import org.apache.camel.attachment.AttachmentMessage;
 import org.apache.camel.attachment.DefaultAttachment;
 import org.apache.camel.component.cxf.common.CxfBinding;
@@ -596,8 +598,9 @@ public class DefaultCxfBinding implements CxfBinding, 
HeaderFilterStrategyAware
             // Create a new MessageContentsList to avoid OOM from the 
HolderOutInterceptor
             if (outBody instanceof List) {
                 resList = new MessageContentsList((List<?>) outBody);
-            } else if (outBody.getClass().isArray()) {
-                resList = new MessageContentsList((Object[]) outBody);
+            } else if (outBody instanceof Object[] array) {
+                // a primitive array such as byte[] is one value, and is 
wrapped below
+                resList = new MessageContentsList(array);
             } else {
                 resList = new MessageContentsList(outBody);
             }
@@ -1204,11 +1207,41 @@ public class DefaultCxfBinding implements CxfBinding, 
HeaderFilterStrategyAware
         if (dataFormat == DataFormat.POJO) {
             answer = out.getBody();
         } else if (dataFormat == DataFormat.PAYLOAD) {
-            answer = out.getBody(CxfPayload.class);
+            answer = getMandatoryBodyIfPresent(out, CxfPayload.class);
         } else if (dataFormat.dealias() == DataFormat.RAW) {
-            answer = out.getBody(InputStream.class);
+            answer = getMandatoryBodyIfPresent(out, InputStream.class);
         } else if (dataFormat.dealias() == DataFormat.CXF_MESSAGE) {
-            answer = out.getBody();
+            Object body = out.getBody();
+            // a List or an array holds the SOAP message as its first element
+            answer = body instanceof List || body instanceof Object[] ? body : 
toCxfMessageBody(out);
+        }
+        return answer;
+    }
+
+    /**
+     * The body as the SOAP envelope for the CXF_MESSAGE data format: a {@link 
SOAPMessage} or a {@link Source} as it
+     * is, and any other body (such as text, bytes, a stream or a DOM document 
of the envelope) converted to a
+     * {@link DOMSource}, failing when it cannot be converted.
+     */
+    static Object toCxfMessageBody(org.apache.camel.Message message) {
+        Object body = message.getBody();
+        if (body == null || body instanceof SOAPMessage || body instanceof 
Source) {
+            return body;
+        }
+        return getMandatoryBodyIfPresent(message, DOMSource.class);
+    }
+
+    /**
+     * The body converted to the given type, null for a null body, and failing 
when a body cannot be converted, so it is
+     * not sent as an empty message.
+     */
+    private static <T> T getMandatoryBodyIfPresent(org.apache.camel.Message 
message, Class<T> type) {
+        if (message.getBody() == null) {
+            return null;
+        }
+        T answer = message.getBody(type);
+        if (answer == null) {
+            throw new InvalidPayloadRuntimeException(message.getExchange(), 
type, message);
         }
         return answer;
     }
diff --git 
a/components/camel-cxf/camel-cxf-soap/src/test/java/org/apache/camel/component/cxf/jaxws/BodyTypeService.java
 
b/components/camel-cxf/camel-cxf-soap/src/test/java/org/apache/camel/component/cxf/jaxws/BodyTypeService.java
new file mode 100644
index 000000000000..91ebd1e48f3c
--- /dev/null
+++ 
b/components/camel-cxf/camel-cxf-soap/src/test/java/org/apache/camel/component/cxf/jaxws/BodyTypeService.java
@@ -0,0 +1,25 @@
+/*
+ * 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.cxf.jaxws;
+
+public interface BodyTypeService {
+
+    String echoText(String text);
+
+    byte[] echoBytes(byte[] data);
+
+}
diff --git 
a/components/camel-cxf/camel-cxf-soap/src/test/java/org/apache/camel/component/cxf/jaxws/CxfBodyTypesTest.java
 
b/components/camel-cxf/camel-cxf-soap/src/test/java/org/apache/camel/component/cxf/jaxws/CxfBodyTypesTest.java
new file mode 100644
index 000000000000..684cea061f92
--- /dev/null
+++ 
b/components/camel-cxf/camel-cxf-soap/src/test/java/org/apache/camel/component/cxf/jaxws/CxfBodyTypesTest.java
@@ -0,0 +1,174 @@
+/*
+ * 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.cxf.jaxws;
+
+import java.io.ByteArrayInputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.camel.Exchange;
+import org.apache.camel.InvalidPayloadRuntimeException;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.cxf.common.message.CxfConstants;
+import org.apache.camel.test.AvailablePortFinder;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.apache.cxf.BusFactory;
+import org.apache.cxf.frontend.ClientProxyFactoryBean;
+import org.apache.cxf.message.MessageContentsList;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * The message bodies the cxf producer sends and the cxf consumer replies in 
each data format.
+ */
+public class CxfBodyTypesTest extends CamelTestSupport {
+
+    @RegisterExtension
+    static final AvailablePortFinder.Port PORT = AvailablePortFinder.find();
+
+    private static final String NS = 
"http://jaxws.cxf.component.camel.apache.org/";;
+
+    private static final String ECHO_TEXT_REQUEST = "<soap:Envelope 
xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\";>"
+                                                    + 
"<soap:Body><ns1:echoText xmlns:ns1=\"" + NS + "\">"
+                                                    + "<arg0 xmlns=\"" + NS + 
"\">Hello World</arg0>"
+                                                    + 
"</ns1:echoText></soap:Body></soap:Envelope>";
+
+    private static final String ECHO_TEXT_RESPONSE = "<soap:Envelope 
xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\";>"
+                                                     + 
"<soap:Body><ns1:echoTextResponse xmlns:ns1=\"" + NS + "\">"
+                                                     + "<return xmlns=\"" + NS 
+ "\">echo from envelope</return>"
+                                                     + 
"</ns1:echoTextResponse></soap:Body></soap:Envelope>";
+
+    private static String address(String path) {
+        return "http://localhost:"; + PORT.getPort() + "/CxfBodyTypesTest/" + 
path;
+    }
+
+    private static String uri(String path) {
+        return "cxf://" + address(path) + "?serviceClass=" + 
BodyTypeService.class.getName();
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            public void configure() {
+                from(uri("pojo")).process(CxfBodyTypesTest::echo);
+
+                from(uri("payload") + 
"&dataFormat=PAYLOAD").setBody(constant(Map.of("foo", "bar")));
+
+                from(uri("raw") + 
"&dataFormat=RAW").setBody(constant(Map.of("foo", "bar")));
+
+                from(uri("message") + "&dataFormat=CXF_MESSAGE")
+                        .process(e -> e.getMessage().setBody(
+                                new 
ByteArrayInputStream(ECHO_TEXT_RESPONSE.getBytes(StandardCharsets.UTF_8))));
+
+                from("direct:pojo").to(uri("pojo") + 
"&defaultOperationName=echoText");
+
+                from("direct:message").to(uri("pojo") + 
"&dataFormat=CXF_MESSAGE&defaultOperationName=echoText");
+            }
+        };
+    }
+
+    private static void echo(Exchange exchange) {
+        String operation = 
exchange.getIn().getHeader(CxfConstants.OPERATION_NAME, String.class);
+        Object arg = 
exchange.getIn().getBody(MessageContentsList.class).get(0);
+        if ("echoBytes".equals(operation)) {
+            exchange.getMessage().setBody(("echo " + new String((byte[]) arg, 
StandardCharsets.UTF_8))
+                    .getBytes(StandardCharsets.UTF_8));
+        } else {
+            exchange.getMessage().setBody("echo " + arg);
+        }
+    }
+
+    private static BodyTypeService client(String path) {
+        ClientProxyFactoryBean proxyFactory = new ClientProxyFactoryBean();
+        proxyFactory.getClientFactoryBean().setAddress(address(path));
+        
proxyFactory.getClientFactoryBean().setServiceClass(BodyTypeService.class);
+        proxyFactory.getClientFactoryBean().setBus(BusFactory.getDefaultBus());
+        return (BodyTypeService) proxyFactory.create();
+    }
+
+    @Test
+    public void testPojoConsumerRepliesByteArray() {
+        byte[] reply = 
client("pojo").echoBytes("Hello".getBytes(StandardCharsets.UTF_8));
+
+        assertEquals("echo Hello", new String(reply, StandardCharsets.UTF_8));
+    }
+
+    @Test
+    public void testPojoProducerSendsTextWithCommaAsOneParameter() {
+        Object reply = template.requestBody("direct:pojo", "Hello,World");
+
+        assertEquals("echo Hello,World", ((List<?>) reply).get(0));
+    }
+
+    @Test
+    public void testPojoProducerSendsByteArrayAsOneParameter() {
+        Object reply = template.requestBodyAndHeader("direct:pojo", 
"Hello".getBytes(StandardCharsets.UTF_8),
+                CxfConstants.OPERATION_NAME, "echoBytes");
+
+        assertArrayEquals("echo Hello".getBytes(StandardCharsets.UTF_8), 
(byte[]) ((List<?>) reply).get(0));
+    }
+
+    @Test
+    public void testPojoProducerSendsListAsParameters() {
+        Object reply = template.requestBody("direct:pojo", 
List.of("Hello,World"));
+
+        assertEquals("echo Hello,World", ((List<?>) reply).get(0));
+    }
+
+    @Test
+    public void testCxfMessageProducerSendsTextEnvelope() {
+        String reply = template.requestBody("direct:message", 
ECHO_TEXT_REQUEST, String.class);
+
+        assertTrue(reply.contains("echo Hello World"), "Wrong reply: " + 
reply);
+    }
+
+    @Test
+    public void testCxfMessageProducerFailsForBodyThatIsNotAnEnvelope() {
+        Exception e = assertThrows(Exception.class,
+                () -> template.requestBody("direct:message", Map.of("foo", 
"bar")));
+
+        assertInstanceOf(InvalidPayloadRuntimeException.class, e.getCause());
+        assertTrue(e.getCause().getMessage().contains("DOMSource"), "Wrong 
exception: " + e.getCause().getMessage());
+    }
+
+    @Test
+    public void testCxfMessageConsumerRepliesStreamEnvelope() {
+        assertEquals("echo from envelope", 
client("message").echoText("Hello"));
+    }
+
+    @Test
+    public void testPayloadConsumerFailsForReplyThatDoesNotConvert() {
+        Exception e = assertThrows(Exception.class, () -> 
client("payload").echoText("Hello"));
+
+        assertTrue(e.getMessage().contains("CxfPayload"), "Wrong fault: " + 
e.getMessage());
+    }
+
+    @Test
+    public void testRawConsumerFailsForReplyThatDoesNotConvert() {
+        Exception e = assertThrows(Exception.class, () -> 
client("raw").echoText("Hello"));
+
+        assertTrue(e.getMessage().contains("InputStream"), "Wrong fault: " + 
e.getMessage());
+    }
+
+}
diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
index 139a76288316..990a5de7e5a8 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
@@ -2045,6 +2045,23 @@ the previous output, convert the rows to `List` before 
marshalling, or set a cus
 now has all the columns seen so far in the body, so rows that each have keys 
of their own (sparse or pivoted data)
 give about N²/2 fields for N rows, where the output was linear in the input 
before.
 
+=== camel-cxf - message bodies of the POJO producer and of CXF_MESSAGE, and 
replies that do not convert
+
+The `cxf` producer in `POJO` mode split a body that is not a `List` or an 
`Object[]` into several parameters, the way
+the Split EIP does: a `String` with a comma (`"Hello,World"`) became two 
parameters, a `byte[]` one parameter per
+byte. Now a `List` (or another `Collection`) or an `Object[]` holds the 
parameters, and any other body, such as a
+`String`, a `byte[]` or a `Map`, is the one parameter. A route that relied on 
a comma-separated `String` (or another
+iterable value) to give several parameters must now set a `List` of the 
parameters as the body.
+
+In `CXF_MESSAGE` mode, the producer and the consumer reply sent a body that is 
not a `SOAPMessage` or a
+`javax.xml.transform.Source` as an empty SOAP envelope. Such a body (for 
example the envelope as a `String`, `byte[]`,
+`InputStream` or DOM `Document`) is now converted to a `DOMSource` and sent; a 
body that cannot be converted fails the
+exchange.
+
+A consumer reply in `PAYLOAD` mode that does not convert to a `CxfPayload`, or 
in `RAW` mode that does not convert to
+an `InputStream`, was sent as an empty `soap:Body` or an empty response. It is 
now a SOAP fault. A `null` reply is
+unchanged.
+
 === camel-github2 - the commit consumer routes the commits beyond the newest 
100
 
 The `commit` consumer read only the 100 newest commits of the branch at each 
poll, so when more than 100 commits were

Reply via email to