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 f3f949af1e94 CAMEL-26159: camel-salesforce - send a Map or POJO body 
as JSON, fail raw on a body it cannot send (#28942)
f3f949af1e94 is described below

commit f3f949af1e94b742b2077744b346754d460eb6a9
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Nov 2 21:47:46 2026 +0100

    CAMEL-26159: camel-salesforce - send a Map or POJO body as JSON, fail raw 
on a body it cannot send (#28942)
    
    * CAMEL-26159: camel-salesforce - send a Map or POJO body as JSON, fail…
    
    … raw on a body it cannot send
    
    The REST operations sent a Map or another non-DTO object as its toString
    (for example {Name=x}); they now marshal it with the object mapper, as for
    DTOs. Text and simple values are still sent as is.
    
    The raw operation sent an empty request when the body could not be
    converted to an InputStream (such as a Map); it now fails with an
    InvalidPayloadException.
    
    The SObject Collections operations completed the exchange with no
    exception and no body when the response could not be parsed; they now set
    a SalesforceException.
    
    The rawPayload option description now says the REST producer returns an
    InputStream, not a String.
    
    * CAMEL-26159: camel-salesforce - fail the composite operations on an u…
    
    …nparseable response
    
    The composite, composite-batch and composite-tree operations set a null
    exception when the response was empty or could not be parsed, so the
    exchange completed with no exception and no response. They now set a
    SalesforceException, as the SObject Collections operations do.
    
    * Merge remote-tracking branch 'origin/main' into CAMEL-26159-salesforc…
    
    …e-map-body
    
    
    # Conflicts:
    #       docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../camel/catalog/components/salesforce.json       |   6 +-
 .../camel/catalog/docs/salesforce-component.adoc   |   2 +-
 .../camel/catalog/docs/salesforce-rest-api.adoc    |   3 +
 .../camel/component/salesforce/salesforce.json     |   6 +-
 .../src/main/docs/salesforce-component.adoc        |   2 +-
 .../src/main/docs/salesforce-rest-api.adoc         |   3 +
 .../component/salesforce/SalesforceEndpoint.java   |   9 +-
 .../salesforce/SalesforceEndpointConfig.java       |   5 +-
 .../internal/processor/CompositeApiProcessor.java  |  20 +-
 .../CompositeSObjectCollectionsProcessor.java      |   3 +
 .../internal/processor/JsonRestProcessor.java      |  13 ++
 .../internal/processor/RawProcessor.java           |   4 +-
 .../salesforce/ProducerRequestBodyTest.java        | 205 +++++++++++++++++++++
 .../internal/processor/JsonRestProcessorTest.java  |  49 +++++
 .../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc    |   9 +
 .../dsl/SalesforceComponentBuilderFactory.java     |   6 +-
 .../dsl/SalesforceEndpointBuilderFactory.java      |  36 ++--
 17 files changed, 348 insertions(+), 33 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/salesforce.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/salesforce.json
index 14820edd6d69..250fccb2073c 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/salesforce.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/salesforce.json
@@ -62,7 +62,7 @@
     "pkChunkingParent": { "index": 34, "kind": "property", "displayName": "Pk 
Chunking Parent", "group": "common", "label": "", "required": false, "type": 
"string", "javaType": "java.lang.String", "deprecated": false, "autowired": 
false, "secret": false, "configurationClass": 
"org.apache.camel.component.salesforce.SalesforceEndpointConfig", 
"configurationField": "config", "description": "Specifies the parent object 
when you're enabling PK chunking for queries on sharing objects. The chun [...]
     "pkChunkingStartRow": { "index": 35, "kind": "property", "displayName": 
"Pk Chunking Start Row", "group": "common", "label": "", "required": false, 
"type": "string", "javaType": "java.lang.String", "deprecated": false, 
"autowired": false, "secret": false, "configurationClass": 
"org.apache.camel.component.salesforce.SalesforceEndpointConfig", 
"configurationField": "config", "description": "Specifies the 15-character or 
18-character record ID to be used as the lower boundary for the fi [...]
     "queryLocator": { "index": 36, "kind": "property", "displayName": "Query 
Locator", "group": "common", "label": "", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": false, "configurationClass": 
"org.apache.camel.component.salesforce.SalesforceEndpointConfig", 
"configurationField": "config", "description": "Query Locator provided by 
salesforce for use when a query results in more records than can be retrieved 
in a s [...]
-    "rawPayload": { "index": 37, "kind": "property", "displayName": "Raw 
Payload", "group": "common", "label": "", "required": false, "type": "boolean", 
"javaType": "boolean", "deprecated": false, "autowired": false, "secret": 
false, "defaultValue": false, "configurationClass": 
"org.apache.camel.component.salesforce.SalesforceEndpointConfig", 
"configurationField": "config", "description": "Use raw payload String for 
request and response (either JSON or XML depending on format), instead o [...]
+    "rawPayload": { "index": 37, "kind": "property", "displayName": "Raw 
Payload", "group": "common", "label": "", "required": false, "type": "boolean", 
"javaType": "boolean", "deprecated": false, "autowired": false, "secret": 
false, "defaultValue": false, "configurationClass": 
"org.apache.camel.component.salesforce.SalesforceEndpointConfig", 
"configurationField": "config", "description": "Use raw payload for request and 
response, instead of DTOs, false by default. The REST, apexCall and [...]
     "reportId": { "index": 38, "kind": "property", "displayName": "Report Id", 
"group": "common", "label": "", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": false, "configurationClass": 
"org.apache.camel.component.salesforce.SalesforceEndpointConfig", 
"configurationField": "config", "description": "Salesforce1 Analytics report 
Id" },
     "reportMetadata": { "index": 39, "kind": "property", "displayName": 
"Report Metadata", "group": "common", "label": "", "required": false, "type": 
"object", "javaType": 
"org.apache.camel.component.salesforce.api.dto.analytics.reports.ReportMetadata",
 "deprecated": false, "autowired": false, "secret": false, 
"configurationClass": 
"org.apache.camel.component.salesforce.SalesforceEndpointConfig", 
"configurationField": "config", "description": "Salesforce1 Analytics report 
metadata for fi [...]
     "resultId": { "index": 40, "kind": "property", "displayName": "Result Id", 
"group": "common", "label": "", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": false, "configurationClass": 
"org.apache.camel.component.salesforce.SalesforceEndpointConfig", 
"configurationField": "config", "description": "Identifies one result set of a 
completed Bulk API v1 query batch, as returned by the getQueryResultIds 
operation. Used [...]
@@ -162,7 +162,7 @@
   },
   "bodyTypes": {
     "consumerOut": { "carriers": [ "map", "pojo" ], "byOption": [ { "option": 
"operationName", "value": "pubSubSubscribe", "carriers": [ "pojo" ] }, { 
"option": "rawPayload", "value": "true", "carriers": [ "text", "pojo" ] }, { 
"option": "pubSubDeserializeType", "value": "JSON", "carriers": [ "text", 
"map", "pojo" ] } ], "description": "The subscribe operation gives a push topic 
record as a Map, or an object of sObjectClass (or the DTO of sObjectName), a 
platform event (\/event\/) as a P [...]
-    "producerIn": { "carriers": [ "text", "bytes", "stream", "file", "pojo", 
"list", "map", "null" ], "description": "The REST operations send a generated 
DTO (an AbstractSObjectBase) as JSON, or JSON text, bytes, a stream or a file 
as is; a Map or another object is sent as its toString, not as JSON: marshal it 
first. Text in the body is the id, SOQL or SOSL when the option or header is 
not set. apexCall takes a Map as query parameters; bulk operations take a 
JobInfo, BatchInfo or Job, a [...]
+    "producerIn": { "carriers": [ "text", "bytes", "stream", "file", "pojo", 
"list", "map", "null" ], "description": "The REST operations send a generated 
DTO (an AbstractSObjectBase), a Map or another object as JSON, or JSON text, 
bytes, a stream or a file as is. Text in the body is the id, SOQL or SOSL when 
the option or header is not set. apexCall takes a Map as query parameters; bulk 
operations take a JobInfo, BatchInfo or Job, and createBatch the CSV or XML 
records as a stream; comp [...]
     "producerOut": { "carriers": [ "pojo", "list", "map", "stream", 
"unchanged", "null" ], "dependsOn": [ "streamQueryResult", "notFoundBehaviour" 
], "byOption": [ { "option": "operationName", "value": "createSObject", 
"carriers": [ "pojo" ], "javaType": 
"org.apache.camel.component.salesforce.api.dto.CreateSObjectResult" }, { 
"option": "operationName", "value": "upsertSObject", "carriers": [ "pojo", 
"unchanged" ] }, { "option": "operationName", "value": "updateSObject", 
"carriers": [ "un [...]
   },
   "properties": {
@@ -199,7 +199,7 @@
     "pkChunkingParent": { "index": 30, "kind": "parameter", "displayName": "Pk 
Chunking Parent", "group": "common", "label": "", "required": false, "type": 
"string", "javaType": "java.lang.String", "deprecated": false, "autowired": 
false, "secret": false, "configurationClass": 
"org.apache.camel.component.salesforce.SalesforceEndpointConfig", 
"configurationField": "configuration", "description": "Specifies the parent 
object when you're enabling PK chunking for queries on sharing objects.  [...]
     "pkChunkingStartRow": { "index": 31, "kind": "parameter", "displayName": 
"Pk Chunking Start Row", "group": "common", "label": "", "required": false, 
"type": "string", "javaType": "java.lang.String", "deprecated": false, 
"autowired": false, "secret": false, "configurationClass": 
"org.apache.camel.component.salesforce.SalesforceEndpointConfig", 
"configurationField": "configuration", "description": "Specifies the 
15-character or 18-character record ID to be used as the lower boundary fo [...]
     "queryLocator": { "index": 32, "kind": "parameter", "displayName": "Query 
Locator", "group": "common", "label": "", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": false, "configurationClass": 
"org.apache.camel.component.salesforce.SalesforceEndpointConfig", 
"configurationField": "configuration", "description": "Query Locator provided 
by salesforce for use when a query results in more records than can be retrieve 
[...]
-    "rawPayload": { "index": 33, "kind": "parameter", "displayName": "Raw 
Payload", "group": "common", "label": "", "required": false, "type": "boolean", 
"javaType": "boolean", "deprecated": false, "autowired": false, "secret": 
false, "defaultValue": false, "configurationClass": 
"org.apache.camel.component.salesforce.SalesforceEndpointConfig", 
"configurationField": "configuration", "description": "Use raw payload String 
for request and response (either JSON or XML depending on format), i [...]
+    "rawPayload": { "index": 33, "kind": "parameter", "displayName": "Raw 
Payload", "group": "common", "label": "", "required": false, "type": "boolean", 
"javaType": "boolean", "deprecated": false, "autowired": false, "secret": 
false, "defaultValue": false, "configurationClass": 
"org.apache.camel.component.salesforce.SalesforceEndpointConfig", 
"configurationField": "configuration", "description": "Use raw payload for 
request and response, instead of DTOs, false by default. The REST, apex [...]
     "reportId": { "index": 34, "kind": "parameter", "displayName": "Report 
Id", "group": "common", "label": "", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": false, "configurationClass": 
"org.apache.camel.component.salesforce.SalesforceEndpointConfig", 
"configurationField": "configuration", "description": "Salesforce1 Analytics 
report Id" },
     "reportMetadata": { "index": 35, "kind": "parameter", "displayName": 
"Report Metadata", "group": "common", "label": "", "required": false, "type": 
"object", "javaType": 
"org.apache.camel.component.salesforce.api.dto.analytics.reports.ReportMetadata",
 "deprecated": false, "autowired": false, "secret": false, 
"configurationClass": 
"org.apache.camel.component.salesforce.SalesforceEndpointConfig", 
"configurationField": "configuration", "description": "Salesforce1 Analytics 
report metadat [...]
     "resultId": { "index": 36, "kind": "parameter", "displayName": "Result 
Id", "group": "common", "label": "", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": false, "configurationClass": 
"org.apache.camel.component.salesforce.SalesforceEndpointConfig", 
"configurationField": "configuration", "description": "Identifies one result 
set of a completed Bulk API v1 query batch, as returned by the 
getQueryResultIds operati [...]
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/salesforce-component.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/salesforce-component.adoc
index a40ad7f27145..6e636d2f7688 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/salesforce-component.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/salesforce-component.adoc
@@ -267,7 +267,7 @@ header will be automatically set based on the `format` 
option, but this can be o
 |===
 | Parameter | Type | Description| Default| Required
 
-| Body | `String` or `InputStream` | Body of the HTTP request | |
+| Body | `String` or `InputStream` | Body of the HTTP request. A body that 
cannot be converted to an `InputStream`, such as a `Map`, fails with an 
`InvalidPayloadException`. | |
 | `rawPath` | `String` | The portion of the endpoint URL after the domain 
name, e.g., '/services/data/v51.0/sobjects/Account/' | | x
 | `rawMethod` | `String` | The HTTP method | | x
 | `rawQueryParameters` | `String` | Comma separated list of message headers to 
include as query parameters. Do not url-encode values as this will be done 
automatically. | |
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/salesforce-rest-api.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/salesforce-rest-api.adoc
index 4f7dcb91491b..7d446454ca6d 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/salesforce-rest-api.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/salesforce-rest-api.adoc
@@ -41,6 +41,9 @@ request as the input to a subsequent request.
 
 Unless otherwise specified, DTO types for the following options are from  
`org.apache.camel.component.salesforce.api.dto` or one if its sub-packages.
 
+The operations that send a request body (such as `createSObject`, 
`updateSObject` and `upsertSObject`) send an SObject DTO,
+a `Map` or another object as JSON, and JSON text, bytes, a stream or a file as 
is.
+
 [[getVersions]]
 == Versions
 
diff --git 
a/components/camel-salesforce/camel-salesforce-component/src/generated/resources/META-INF/org/apache/camel/component/salesforce/salesforce.json
 
b/components/camel-salesforce/camel-salesforce-component/src/generated/resources/META-INF/org/apache/camel/component/salesforce/salesforce.json
index 14820edd6d69..250fccb2073c 100644
--- 
a/components/camel-salesforce/camel-salesforce-component/src/generated/resources/META-INF/org/apache/camel/component/salesforce/salesforce.json
+++ 
b/components/camel-salesforce/camel-salesforce-component/src/generated/resources/META-INF/org/apache/camel/component/salesforce/salesforce.json
@@ -62,7 +62,7 @@
     "pkChunkingParent": { "index": 34, "kind": "property", "displayName": "Pk 
Chunking Parent", "group": "common", "label": "", "required": false, "type": 
"string", "javaType": "java.lang.String", "deprecated": false, "autowired": 
false, "secret": false, "configurationClass": 
"org.apache.camel.component.salesforce.SalesforceEndpointConfig", 
"configurationField": "config", "description": "Specifies the parent object 
when you're enabling PK chunking for queries on sharing objects. The chun [...]
     "pkChunkingStartRow": { "index": 35, "kind": "property", "displayName": 
"Pk Chunking Start Row", "group": "common", "label": "", "required": false, 
"type": "string", "javaType": "java.lang.String", "deprecated": false, 
"autowired": false, "secret": false, "configurationClass": 
"org.apache.camel.component.salesforce.SalesforceEndpointConfig", 
"configurationField": "config", "description": "Specifies the 15-character or 
18-character record ID to be used as the lower boundary for the fi [...]
     "queryLocator": { "index": 36, "kind": "property", "displayName": "Query 
Locator", "group": "common", "label": "", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": false, "configurationClass": 
"org.apache.camel.component.salesforce.SalesforceEndpointConfig", 
"configurationField": "config", "description": "Query Locator provided by 
salesforce for use when a query results in more records than can be retrieved 
in a s [...]
-    "rawPayload": { "index": 37, "kind": "property", "displayName": "Raw 
Payload", "group": "common", "label": "", "required": false, "type": "boolean", 
"javaType": "boolean", "deprecated": false, "autowired": false, "secret": 
false, "defaultValue": false, "configurationClass": 
"org.apache.camel.component.salesforce.SalesforceEndpointConfig", 
"configurationField": "config", "description": "Use raw payload String for 
request and response (either JSON or XML depending on format), instead o [...]
+    "rawPayload": { "index": 37, "kind": "property", "displayName": "Raw 
Payload", "group": "common", "label": "", "required": false, "type": "boolean", 
"javaType": "boolean", "deprecated": false, "autowired": false, "secret": 
false, "defaultValue": false, "configurationClass": 
"org.apache.camel.component.salesforce.SalesforceEndpointConfig", 
"configurationField": "config", "description": "Use raw payload for request and 
response, instead of DTOs, false by default. The REST, apexCall and [...]
     "reportId": { "index": 38, "kind": "property", "displayName": "Report Id", 
"group": "common", "label": "", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": false, "configurationClass": 
"org.apache.camel.component.salesforce.SalesforceEndpointConfig", 
"configurationField": "config", "description": "Salesforce1 Analytics report 
Id" },
     "reportMetadata": { "index": 39, "kind": "property", "displayName": 
"Report Metadata", "group": "common", "label": "", "required": false, "type": 
"object", "javaType": 
"org.apache.camel.component.salesforce.api.dto.analytics.reports.ReportMetadata",
 "deprecated": false, "autowired": false, "secret": false, 
"configurationClass": 
"org.apache.camel.component.salesforce.SalesforceEndpointConfig", 
"configurationField": "config", "description": "Salesforce1 Analytics report 
metadata for fi [...]
     "resultId": { "index": 40, "kind": "property", "displayName": "Result Id", 
"group": "common", "label": "", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": false, "configurationClass": 
"org.apache.camel.component.salesforce.SalesforceEndpointConfig", 
"configurationField": "config", "description": "Identifies one result set of a 
completed Bulk API v1 query batch, as returned by the getQueryResultIds 
operation. Used [...]
@@ -162,7 +162,7 @@
   },
   "bodyTypes": {
     "consumerOut": { "carriers": [ "map", "pojo" ], "byOption": [ { "option": 
"operationName", "value": "pubSubSubscribe", "carriers": [ "pojo" ] }, { 
"option": "rawPayload", "value": "true", "carriers": [ "text", "pojo" ] }, { 
"option": "pubSubDeserializeType", "value": "JSON", "carriers": [ "text", 
"map", "pojo" ] } ], "description": "The subscribe operation gives a push topic 
record as a Map, or an object of sObjectClass (or the DTO of sObjectName), a 
platform event (\/event\/) as a P [...]
-    "producerIn": { "carriers": [ "text", "bytes", "stream", "file", "pojo", 
"list", "map", "null" ], "description": "The REST operations send a generated 
DTO (an AbstractSObjectBase) as JSON, or JSON text, bytes, a stream or a file 
as is; a Map or another object is sent as its toString, not as JSON: marshal it 
first. Text in the body is the id, SOQL or SOSL when the option or header is 
not set. apexCall takes a Map as query parameters; bulk operations take a 
JobInfo, BatchInfo or Job, a [...]
+    "producerIn": { "carriers": [ "text", "bytes", "stream", "file", "pojo", 
"list", "map", "null" ], "description": "The REST operations send a generated 
DTO (an AbstractSObjectBase), a Map or another object as JSON, or JSON text, 
bytes, a stream or a file as is. Text in the body is the id, SOQL or SOSL when 
the option or header is not set. apexCall takes a Map as query parameters; bulk 
operations take a JobInfo, BatchInfo or Job, and createBatch the CSV or XML 
records as a stream; comp [...]
     "producerOut": { "carriers": [ "pojo", "list", "map", "stream", 
"unchanged", "null" ], "dependsOn": [ "streamQueryResult", "notFoundBehaviour" 
], "byOption": [ { "option": "operationName", "value": "createSObject", 
"carriers": [ "pojo" ], "javaType": 
"org.apache.camel.component.salesforce.api.dto.CreateSObjectResult" }, { 
"option": "operationName", "value": "upsertSObject", "carriers": [ "pojo", 
"unchanged" ] }, { "option": "operationName", "value": "updateSObject", 
"carriers": [ "un [...]
   },
   "properties": {
@@ -199,7 +199,7 @@
     "pkChunkingParent": { "index": 30, "kind": "parameter", "displayName": "Pk 
Chunking Parent", "group": "common", "label": "", "required": false, "type": 
"string", "javaType": "java.lang.String", "deprecated": false, "autowired": 
false, "secret": false, "configurationClass": 
"org.apache.camel.component.salesforce.SalesforceEndpointConfig", 
"configurationField": "configuration", "description": "Specifies the parent 
object when you're enabling PK chunking for queries on sharing objects.  [...]
     "pkChunkingStartRow": { "index": 31, "kind": "parameter", "displayName": 
"Pk Chunking Start Row", "group": "common", "label": "", "required": false, 
"type": "string", "javaType": "java.lang.String", "deprecated": false, 
"autowired": false, "secret": false, "configurationClass": 
"org.apache.camel.component.salesforce.SalesforceEndpointConfig", 
"configurationField": "configuration", "description": "Specifies the 
15-character or 18-character record ID to be used as the lower boundary fo [...]
     "queryLocator": { "index": 32, "kind": "parameter", "displayName": "Query 
Locator", "group": "common", "label": "", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": false, "configurationClass": 
"org.apache.camel.component.salesforce.SalesforceEndpointConfig", 
"configurationField": "configuration", "description": "Query Locator provided 
by salesforce for use when a query results in more records than can be retrieve 
[...]
-    "rawPayload": { "index": 33, "kind": "parameter", "displayName": "Raw 
Payload", "group": "common", "label": "", "required": false, "type": "boolean", 
"javaType": "boolean", "deprecated": false, "autowired": false, "secret": 
false, "defaultValue": false, "configurationClass": 
"org.apache.camel.component.salesforce.SalesforceEndpointConfig", 
"configurationField": "configuration", "description": "Use raw payload String 
for request and response (either JSON or XML depending on format), i [...]
+    "rawPayload": { "index": 33, "kind": "parameter", "displayName": "Raw 
Payload", "group": "common", "label": "", "required": false, "type": "boolean", 
"javaType": "boolean", "deprecated": false, "autowired": false, "secret": 
false, "defaultValue": false, "configurationClass": 
"org.apache.camel.component.salesforce.SalesforceEndpointConfig", 
"configurationField": "configuration", "description": "Use raw payload for 
request and response, instead of DTOs, false by default. The REST, apex [...]
     "reportId": { "index": 34, "kind": "parameter", "displayName": "Report 
Id", "group": "common", "label": "", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": false, "configurationClass": 
"org.apache.camel.component.salesforce.SalesforceEndpointConfig", 
"configurationField": "configuration", "description": "Salesforce1 Analytics 
report Id" },
     "reportMetadata": { "index": 35, "kind": "parameter", "displayName": 
"Report Metadata", "group": "common", "label": "", "required": false, "type": 
"object", "javaType": 
"org.apache.camel.component.salesforce.api.dto.analytics.reports.ReportMetadata",
 "deprecated": false, "autowired": false, "secret": false, 
"configurationClass": 
"org.apache.camel.component.salesforce.SalesforceEndpointConfig", 
"configurationField": "configuration", "description": "Salesforce1 Analytics 
report metadat [...]
     "resultId": { "index": 36, "kind": "parameter", "displayName": "Result 
Id", "group": "common", "label": "", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": false, "configurationClass": 
"org.apache.camel.component.salesforce.SalesforceEndpointConfig", 
"configurationField": "configuration", "description": "Identifies one result 
set of a completed Bulk API v1 query batch, as returned by the 
getQueryResultIds operati [...]
diff --git 
a/components/camel-salesforce/camel-salesforce-component/src/main/docs/salesforce-component.adoc
 
b/components/camel-salesforce/camel-salesforce-component/src/main/docs/salesforce-component.adoc
index a40ad7f27145..6e636d2f7688 100644
--- 
a/components/camel-salesforce/camel-salesforce-component/src/main/docs/salesforce-component.adoc
+++ 
b/components/camel-salesforce/camel-salesforce-component/src/main/docs/salesforce-component.adoc
@@ -267,7 +267,7 @@ header will be automatically set based on the `format` 
option, but this can be o
 |===
 | Parameter | Type | Description| Default| Required
 
-| Body | `String` or `InputStream` | Body of the HTTP request | |
+| Body | `String` or `InputStream` | Body of the HTTP request. A body that 
cannot be converted to an `InputStream`, such as a `Map`, fails with an 
`InvalidPayloadException`. | |
 | `rawPath` | `String` | The portion of the endpoint URL after the domain 
name, e.g., '/services/data/v51.0/sobjects/Account/' | | x
 | `rawMethod` | `String` | The HTTP method | | x
 | `rawQueryParameters` | `String` | Comma separated list of message headers to 
include as query parameters. Do not url-encode values as this will be done 
automatically. | |
diff --git 
a/components/camel-salesforce/camel-salesforce-component/src/main/docs/salesforce-rest-api.adoc
 
b/components/camel-salesforce/camel-salesforce-component/src/main/docs/salesforce-rest-api.adoc
index 4f7dcb91491b..7d446454ca6d 100644
--- 
a/components/camel-salesforce/camel-salesforce-component/src/main/docs/salesforce-rest-api.adoc
+++ 
b/components/camel-salesforce/camel-salesforce-component/src/main/docs/salesforce-rest-api.adoc
@@ -41,6 +41,9 @@ request as the input to a subsequent request.
 
 Unless otherwise specified, DTO types for the following options are from  
`org.apache.camel.component.salesforce.api.dto` or one if its sub-packages.
 
+The operations that send a request body (such as `createSObject`, 
`updateSObject` and `upsertSObject`) send an SObject DTO,
+a `Map` or another object as JSON, and JSON text, bytes, a stream or a file as 
is.
+
 [[getVersions]]
 == Versions
 
diff --git 
a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/SalesforceEndpoint.java
 
b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/SalesforceEndpoint.java
index ca427742ab57..4acf416e97b8 100644
--- 
a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/SalesforceEndpoint.java
+++ 
b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/SalesforceEndpoint.java
@@ -60,15 +60,16 @@ import org.slf4j.LoggerFactory;
            producerIn = @BodyType(value = {
                    Carrier.TEXT, Carrier.BYTES, Carrier.STREAM, Carrier.FILE, 
Carrier.POJO, Carrier.LIST, Carrier.MAP,
                    Carrier.NULL },
-                                  description = "The REST operations send a 
generated DTO (an AbstractSObjectBase) as"
-                                                + " JSON, or JSON text, bytes, 
a stream or a file as is; a Map or another"
-                                                + " object is sent as its 
toString, not as JSON: marshal it first. Text in"
+                                  description = "The REST operations send a 
generated DTO (an AbstractSObjectBase), a"
+                                                + " Map or another object as 
JSON, or JSON text, bytes, a stream or a file"
+                                                + " as is. Text in"
                                                 + " the body is the id, SOQL 
or SOSL when the option or header is not set."
                                                 + " apexCall takes a Map as 
query parameters; bulk operations take a"
                                                 + " JobInfo, BatchInfo or Job, 
and createBatch the CSV or XML records as a"
                                                 + " stream; composite 
operations an SObjectComposite, SObjectBatch or"
                                                 + " SObjectTree, the SObject 
collections a List of DTOs; pubSubPublish a"
-                                                + " List of events; raw the 
request as a stream"),
+                                                + " List of events; raw the 
request as a stream (a body that cannot be"
+                                                + " converted to a stream, 
such as a Map, fails)"),
            producerOut = @BodyType(value = {
                    Carrier.POJO, Carrier.LIST, Carrier.MAP, Carrier.STREAM, 
Carrier.UNCHANGED, Carrier.NULL },
                                    dependsOn = { "streamQueryResult", 
"notFoundBehaviour" },
diff --git 
a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/SalesforceEndpointConfig.java
 
b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/SalesforceEndpointConfig.java
index 4ab658b1f139..07ae447d4030 100644
--- 
a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/SalesforceEndpointConfig.java
+++ 
b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/SalesforceEndpointConfig.java
@@ -327,8 +327,9 @@ public class SalesforceEndpointConfig implements Cloneable {
     }
 
     /**
-     * Use raw payload {@link String} for request and response (either JSON or 
XML depending on {@code format}), instead
-     * of DTOs, false by default
+     * Use raw payload for request and response, instead of DTOs, false by 
default. The REST, apexCall and composite
+     * operations return the response as is, as an {@code InputStream}, and 
the streaming subscribe consumer gives the
+     * message as JSON text.
      */
     public void setRawPayload(boolean rawPayload) {
         this.rawPayload = rawPayload;
diff --git 
a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/processor/CompositeApiProcessor.java
 
b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/processor/CompositeApiProcessor.java
index 584448170db6..2037e6e01a7f 100644
--- 
a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/processor/CompositeApiProcessor.java
+++ 
b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/processor/CompositeApiProcessor.java
@@ -106,7 +106,7 @@ public final class CompositeApiProcessor extends 
AbstractSalesforceProcessor {
             final SalesforceException exception, final AsyncCallback callback) 
{
         try {
             if (!responseBody.isPresent()) {
-                exchange.setException(exception);
+                exchange.setException(exceptionForEmptyResponse(exception));
             } else {
                 final Message in = exchange.getIn();
                 final Message out = exchange.getOut();
@@ -130,7 +130,7 @@ public final class CompositeApiProcessor extends 
AbstractSalesforceProcessor {
             final SalesforceException exception, final AsyncCallback callback) 
{
         try {
             if (!responseBody.isPresent()) {
-                exchange.setException(exception);
+                exchange.setException(exceptionForEmptyResponse(exception));
             } else {
                 final Message in = exchange.getIn();
                 final Message out = exchange.getOut();
@@ -154,7 +154,7 @@ public final class CompositeApiProcessor extends 
AbstractSalesforceProcessor {
             final SalesforceException exception, final AsyncCallback callback) 
{
         try {
             if (!responseBody.isPresent()) {
-                exchange.setException(exception);
+                exchange.setException(exceptionForEmptyResponse(exception));
             } else {
                 final Message in = exchange.getIn();
                 final Message out = exchange.getOut();
@@ -179,7 +179,7 @@ public final class CompositeApiProcessor extends 
AbstractSalesforceProcessor {
 
         try {
             if (!responseBody.isPresent()) {
-                exchange.setException(exception);
+                exchange.setException(exceptionForEmptyResponse(exception));
             } else {
 
                 final Message in = exchange.getIn();
@@ -214,6 +214,18 @@ public final class CompositeApiProcessor extends 
AbstractSalesforceProcessor {
         }
     }
 
+    /**
+     * The exception to set when the response is empty or could not be parsed: 
the given one, or one that says so, as
+     * the exchange must not complete without a response and without an 
exception.
+     */
+    private static SalesforceException exceptionForEmptyResponse(final 
SalesforceException exception) {
+        if (exception != null) {
+            return exception;
+        }
+        return new SalesforceException(
+                "Unable to read the response of the Composite API: the 
response is empty or not valid JSON");
+    }
+
     <T, R> boolean processInternal(
             final Class<T> bodyType, final Exchange exchange, final 
CompositeApiClient.Operation<T, R> clientOperation,
             final ResponseHandler<R> responseHandler, final AsyncCallback 
callback)
diff --git 
a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/processor/CompositeSObjectCollectionsProcessor.java
 
b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/processor/CompositeSObjectCollectionsProcessor.java
index 4627a02d830e..308dcecd99d5 100644
--- 
a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/processor/CompositeSObjectCollectionsProcessor.java
+++ 
b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/processor/CompositeSObjectCollectionsProcessor.java
@@ -174,6 +174,9 @@ public class CompositeSObjectCollectionsProcessor extends 
AbstractSalesforceProc
         try {
             if (exception != null) {
                 exchange.setException(exception);
+            } else if (responseBody.isEmpty()) {
+                exchange.setException(new SalesforceException(
+                        "Unable to read the response of the SObject 
Collections API: the response is empty or not valid JSON"));
             } else {
                 Message in = exchange.getIn();
                 Message out = exchange.getOut();
diff --git 
a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/processor/JsonRestProcessor.java
 
b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/processor/JsonRestProcessor.java
index 5803a05fbd03..894d6127cb79 100644
--- 
a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/processor/JsonRestProcessor.java
+++ 
b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/processor/JsonRestProcessor.java
@@ -145,9 +145,13 @@ public class JsonRestProcessor extends 
AbstractRestProcessor {
         request = in.getBody(InputStream.class);
         if (request == null) {
             AbstractDTOBase dto = in.getBody(AbstractDTOBase.class);
+            final Object payload = in.getBody();
             if (dto != null) {
                 // marshall the DTO
                 request = getRequestStream(in, dto);
+            } else if (payload != null && !isTextOrSimpleValue(payload)) {
+                // marshall a Map or another POJO, as its toString is not JSON
+                request = getRequestStream(in, payload);
             } else {
                 // if all else fails, get body as String
                 final String body = in.getBody(String.class);
@@ -163,6 +167,15 @@ public class JsonRestProcessor extends 
AbstractRestProcessor {
         return request;
     }
 
+    /**
+     * Whether the body is text or a simple value (number, boolean, enum), 
which is sent as its String value. A Map or
+     * another POJO is marshalled as JSON instead, as its toString is not JSON.
+     */
+    private static boolean isTextOrSimpleValue(Object body) {
+        return body instanceof CharSequence || body instanceof Number || body 
instanceof Boolean
+                || body instanceof Character || body instanceof Enum;
+    }
+
     @Override
     protected InputStream getRequestStream(final Message in, final Object 
object) throws SalesforceException {
         final ByteArrayOutputStream out = new ByteArrayOutputStream();
diff --git 
a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/processor/RawProcessor.java
 
b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/processor/RawProcessor.java
index 126064450516..15519d7579b1 100644
--- 
a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/processor/RawProcessor.java
+++ 
b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/processor/RawProcessor.java
@@ -86,7 +86,9 @@ public class RawProcessor extends AbstractSalesforceProcessor 
{
                 }
             }
 
-            InputStream body = exchange.getIn().getBody(InputStream.class);
+            // a body that cannot be converted to a stream (such as a Map) 
fails instead of sending an empty request
+            InputStream body = exchange.getIn().getBody() != null
+                    ? exchange.getIn().getMandatoryBody(InputStream.class) : 
null;
             rawClient.makeRequest(method, path.toString(), format, body, 
determineHeaders(exchange),
                     (response, headers, exception) -> {
                         Message in = exchange.getIn();
diff --git 
a/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/ProducerRequestBodyTest.java
 
b/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/ProducerRequestBodyTest.java
new file mode 100644
index 000000000000..da72574033ad
--- /dev/null
+++ 
b/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/ProducerRequestBodyTest.java
@@ -0,0 +1,205 @@
+/*
+ * 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.salesforce;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+import mockwebserver3.Dispatcher;
+import mockwebserver3.MockResponse;
+import mockwebserver3.MockWebServer;
+import mockwebserver3.RecordedRequest;
+import okhttp3.HttpUrl;
+import org.apache.camel.Exchange;
+import org.apache.camel.InvalidPayloadException;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.salesforce.api.SalesforceException;
+import org.apache.camel.component.salesforce.api.dto.CreateSObjectResult;
+import org.apache.camel.component.salesforce.api.dto.composite.SObjectBatch;
+import 
org.apache.camel.component.salesforce.api.dto.composite.SObjectComposite;
+import org.apache.camel.component.salesforce.api.dto.composite.SObjectTree;
+import org.apache.camel.component.salesforce.dto.generated.Merchandise__c;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Checks the request bodies the producer sends, and the errors it reports, 
against a mock Salesforce server.
+ */
+public class ProducerRequestBodyTest extends AbstractSalesforceTestBase {
+
+    private static final String OAUTH2_TOKEN_PATH = "/services/oauth2/token";
+
+    private static final LinkedBlockingQueue<RecordedRequest> REQUESTS = new 
LinkedBlockingQueue<>();
+
+    private static MockWebServer server;
+    private static HttpUrl loginUrl;
+
+    @BeforeAll
+    public static void startServer() throws IOException {
+        server = new MockWebServer();
+        server.setDispatcher(new Dispatcher() {
+            @Override
+            public MockResponse dispatch(RecordedRequest recordedRequest) {
+                final String path = recordedRequest.getUrl().encodedPath();
+                if (path.equals(OAUTH2_TOKEN_PATH)) {
+                    return new MockResponse.Builder()
+                            .code(200)
+                            .body("{ \"access_token\": \"mock_token\", \"id\": 
\"https://login.salesforce.com/id/00D4100000xxxxxxxx/0054100000xxxxxxxx\";, 
\"instance_url\": \""
+                                  + loginUrl + "\"}")
+                            .build();
+                }
+                if (path.startsWith("/services/data/")) {
+                    REQUESTS.add(recordedRequest);
+                }
+                if (path.contains("/composite")) {
+                    // not the JSON of a composite response
+                    return new MockResponse.Builder().code(200).body("not 
json").build();
+                }
+                return new MockResponse.Builder()
+                        .code(201)
+                        .addHeader("Content-Type", "application/json")
+                        .body("{ \"id\": \"001D000000IqhSLIAZ\", \"errors\": 
[], \"success\": true }")
+                        .build();
+            }
+        });
+        server.start();
+        loginUrl = server.url("");
+    }
+
+    @AfterAll
+    public static void shutDownServer() throws IOException {
+        if (server != null) {
+            server.close();
+        }
+    }
+
+    @BeforeEach
+    public void clearRequests() {
+        REQUESTS.clear();
+    }
+
+    @Override
+    protected void createComponent() throws Exception {
+        final SalesforceComponent component = new SalesforceComponent();
+        final SalesforceEndpointConfig config = new SalesforceEndpointConfig();
+        config.setApiVersion(SalesforceEndpointConfig.DEFAULT_VERSION);
+        component.setConfig(config);
+
+        final SalesforceLoginConfig loginConfig = new SalesforceLoginConfig();
+        loginConfig.setClientId("ignored");
+        loginConfig.setClientSecret("ignored");
+        loginConfig.setRefreshToken("ignored");
+        loginConfig.setLoginUrl(loginUrl.toString());
+        component.setLoginConfig(loginConfig);
+        component.setPackages(Merchandise__c.class.getPackage().getName());
+
+        context().addComponent("salesforce", component);
+    }
+
+    @Test
+    public void createSObjectShouldSendMapBodyAsJson() throws Exception {
+        final CreateSObjectResult result = 
template().requestBody("direct:createSObject", Map.of("Name", "x"),
+                CreateSObjectResult.class);
+
+        assertThat(result.getId()).isEqualTo("001D000000IqhSLIAZ");
+        final RecordedRequest request = REQUESTS.poll(10, TimeUnit.SECONDS);
+        assertThat(request).isNotNull();
+        assertThat(request.getMethod()).isEqualTo("POST");
+        assertThat(request.getBody().utf8()).isEqualTo("{\"Name\":\"x\"}");
+    }
+
+    @Test
+    public void rawWithMapBodyShouldFail() {
+        final Exchange exchange = template().request("direct:raw", e -> 
e.getIn().setBody(Map.of("Name", "x")));
+
+        
assertThat(exchange.getException()).isInstanceOf(InvalidPayloadException.class);
+        assertThat(REQUESTS).isEmpty();
+    }
+
+    @Test
+    public void compositeCollectionsWithUnparseableResponseShouldFail() {
+        final Merchandise__c merchandise = new Merchandise__c();
+        merchandise.setName("x");
+
+        final Exchange exchange = 
template().request("direct:compositeCreateSObjectCollections",
+                e -> e.getIn().setBody(List.of(merchandise)));
+
+        
assertThat(exchange.getException()).isInstanceOf(SalesforceException.class)
+                .hasMessageContaining("SObject Collections API");
+    }
+
+    @Test
+    public void compositeWithUnparseableResponseShouldFail() {
+        final SObjectComposite composite = new 
SObjectComposite(SalesforceEndpointConfig.DEFAULT_VERSION, true);
+        composite.addGet("Account", "001D000000IqhSLIAZ", "ref1");
+
+        assertCompositeFails("direct:composite", composite);
+    }
+
+    @Test
+    public void compositeBatchWithUnparseableResponseShouldFail() {
+        final SObjectBatch batch = new 
SObjectBatch(SalesforceEndpointConfig.DEFAULT_VERSION);
+        batch.addGet("Account", "001D000000IqhSLIAZ");
+
+        assertCompositeFails("direct:compositeBatch", batch);
+    }
+
+    @Test
+    public void compositeTreeWithUnparseableResponseShouldFail() {
+        final Merchandise__c merchandise = new Merchandise__c();
+        merchandise.setName("x");
+        final SObjectTree tree = new SObjectTree();
+        tree.addObject(merchandise);
+
+        assertCompositeFails("direct:compositeTree", tree);
+    }
+
+    private void assertCompositeFails(String uri, Object body) {
+        final Exchange exchange = template().request(uri, e -> 
e.getIn().setBody(body));
+
+        
assertThat(exchange.getException()).isInstanceOf(SalesforceException.class)
+                .hasMessageContaining("Composite API");
+    }
+
+    @Override
+    protected RouteBuilder doCreateRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                
from("direct:createSObject").to("salesforce:createSObject?sObjectName=Account");
+
+                from("direct:raw")
+                        
.to("salesforce:raw?format=JSON&rawMethod=POST&rawPath=/services/data/v51.0/sobjects/Account");
+
+                
from("direct:compositeCreateSObjectCollections").to("salesforce:compositeCreateSObjectCollections");
+
+                from("direct:composite").to("salesforce:composite");
+
+                from("direct:compositeBatch").to("salesforce:composite-batch");
+
+                from("direct:compositeTree").to("salesforce:composite-tree");
+            }
+        };
+    }
+}
diff --git 
a/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/internal/processor/JsonRestProcessorTest.java
 
b/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/internal/processor/JsonRestProcessorTest.java
index bc47000a7d16..22f2fe612ce6 100644
--- 
a/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/internal/processor/JsonRestProcessorTest.java
+++ 
b/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/internal/processor/JsonRestProcessorTest.java
@@ -19,9 +19,12 @@ package 
org.apache.camel.component.salesforce.internal.processor;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.io.StringReader;
 import java.nio.charset.StandardCharsets;
 import java.time.ZoneId;
 import java.time.ZonedDateTime;
+import java.util.LinkedHashMap;
+import java.util.Map;
 
 import org.apache.camel.Exchange;
 import org.apache.camel.ExchangePattern;
@@ -112,4 +115,50 @@ public class JsonRestProcessorTest {
             }
         }
     }
+
+    public static class Contact {
+
+        private final String lastName;
+
+        public Contact(String lastName) {
+            this.lastName = lastName;
+        }
+
+        public String getLastName() {
+            return lastName;
+        }
+    }
+
+    @Test
+    public void getRequestStreamShouldMarshalMapAsJson() throws Exception {
+        final Map<String, Object> body = new LinkedHashMap<>();
+        body.put("Name", "x");
+        body.put("NumberOfEmployees", 5);
+
+        
assertThat(requestOf(body)).isEqualTo("{\"Name\":\"x\",\"NumberOfEmployees\":5}");
+    }
+
+    @Test
+    public void getRequestStreamShouldMarshalPojoAsJson() throws Exception {
+        assertThat(requestOf(new 
Contact("Doe"))).isEqualTo("{\"lastName\":\"Doe\"}");
+    }
+
+    @Test
+    public void getRequestStreamShouldSendTextAsIs() throws Exception {
+        assertThat(requestOf("{ \"Name\" : \"x\" }")).isEqualTo("{ \"Name\" : 
\"x\" }");
+        assertThat(requestOf(new StringReader("{ \"Name\" : \"x\" 
}"))).isEqualTo("{ \"Name\" : \"x\" }");
+    }
+
+    private static String requestOf(Object body) throws Exception {
+        final SalesforceComponent comp = new SalesforceComponent();
+        final SalesforceEndpointConfig conf = new SalesforceEndpointConfig();
+        final SalesforceEndpoint endpoint = new SalesforceEndpoint("", comp, 
conf, OperationName.CREATE_SOBJECT, "");
+        try (final JsonRestProcessor jsonRestProcessor = new 
JsonRestProcessor(endpoint)) {
+            final Exchange exchange = new DefaultExchange(new 
DefaultCamelContext(), ExchangePattern.InOut);
+            exchange.getIn().setBody(body);
+            try (InputStream stream = 
jsonRestProcessor.getRequestStream(exchange)) {
+                return IOUtils.toString(stream, StandardCharsets.UTF_8);
+            }
+        }
+    }
 }
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 051f557f6290..a7befadbf7d3 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
@@ -771,6 +771,15 @@ the header.
 A message header with a `null` value is no longer turned into a job parameter. 
Before, such a header failed the job
 launch, because Spring Batch does not accept a job parameter without a value.
 
+=== camel-salesforce - a Map or POJO body is sent as JSON, and raw fails on a 
body it cannot send
+
+The REST operations (such as `createSObject`, `updateSObject` and 
`upsertSObject`) now marshal a `Map` or another
+object that is not an SObject DTO as JSON. Before, they sent its `toString()`, 
for example `{Name=x}`, which is not
+JSON. Text, bytes, a stream or a file in the body are sent as is, as before.
+
+The `raw` operation now fails with an `InvalidPayloadException` when the body 
cannot be converted to an `InputStream`,
+for example a `Map`. Before, it sent the request with an empty body.
+
 === camel-spring-rabbitmq - confirm=auto waits for simple publisher confirms
 
 With the default `confirm=auto`, an InOnly producer now also waits for the 
publisher confirm when the connection
diff --git 
a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/SalesforceComponentBuilderFactory.java
 
b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/SalesforceComponentBuilderFactory.java
index e97ce090673b..992602bddc59 100644
--- 
a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/SalesforceComponentBuilderFactory.java
+++ 
b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/SalesforceComponentBuilderFactory.java
@@ -700,8 +700,10 @@ public interface SalesforceComponentBuilderFactory {
     
         
         /**
-         * Use raw payload String for request and response (either JSON or XML
-         * depending on format), instead of DTOs, false by default.
+         * Use raw payload for request and response, instead of DTOs, false by
+         * default. The REST, apexCall and composite operations return the
+         * response as is, as an InputStream, and the streaming subscribe
+         * consumer gives the message as JSON text.
          * 
          * The option is a: &lt;code&gt;boolean&lt;/code&gt; type.
          * 
diff --git 
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/SalesforceEndpointBuilderFactory.java
 
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/SalesforceEndpointBuilderFactory.java
index c6d98088403b..99ed8c303c99 100644
--- 
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/SalesforceEndpointBuilderFactory.java
+++ 
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/SalesforceEndpointBuilderFactory.java
@@ -906,8 +906,10 @@ public interface SalesforceEndpointBuilderFactory {
             return this;
         }
         /**
-         * Use raw payload String for request and response (either JSON or XML
-         * depending on format), instead of DTOs, false by default.
+         * Use raw payload for request and response, instead of DTOs, false by
+         * default. The REST, apexCall and composite operations return the
+         * response as is, as an InputStream, and the streaming subscribe
+         * consumer gives the message as JSON text.
          * 
          * The option is a: <code>boolean</code> type.
          * 
@@ -922,8 +924,10 @@ public interface SalesforceEndpointBuilderFactory {
             return this;
         }
         /**
-         * Use raw payload String for request and response (either JSON or XML
-         * depending on format), instead of DTOs, false by default.
+         * Use raw payload for request and response, instead of DTOs, false by
+         * default. The REST, apexCall and composite operations return the
+         * response as is, as an InputStream, and the streaming subscribe
+         * consumer gives the message as JSON text.
          * 
          * The option will be converted to a <code>boolean</code> type.
          * 
@@ -2508,8 +2512,10 @@ public interface SalesforceEndpointBuilderFactory {
             return this;
         }
         /**
-         * Use raw payload String for request and response (either JSON or XML
-         * depending on format), instead of DTOs, false by default.
+         * Use raw payload for request and response, instead of DTOs, false by
+         * default. The REST, apexCall and composite operations return the
+         * response as is, as an InputStream, and the streaming subscribe
+         * consumer gives the message as JSON text.
          * 
          * The option is a: <code>boolean</code> type.
          * 
@@ -2524,8 +2530,10 @@ public interface SalesforceEndpointBuilderFactory {
             return this;
         }
         /**
-         * Use raw payload String for request and response (either JSON or XML
-         * depending on format), instead of DTOs, false by default.
+         * Use raw payload for request and response, instead of DTOs, false by
+         * default. The REST, apexCall and composite operations return the
+         * response as is, as an InputStream, and the streaming subscribe
+         * consumer gives the message as JSON text.
          * 
          * The option will be converted to a <code>boolean</code> type.
          * 
@@ -3952,8 +3960,10 @@ public interface SalesforceEndpointBuilderFactory {
             return this;
         }
         /**
-         * Use raw payload String for request and response (either JSON or XML
-         * depending on format), instead of DTOs, false by default.
+         * Use raw payload for request and response, instead of DTOs, false by
+         * default. The REST, apexCall and composite operations return the
+         * response as is, as an InputStream, and the streaming subscribe
+         * consumer gives the message as JSON text.
          * 
          * The option is a: <code>boolean</code> type.
          * 
@@ -3968,8 +3978,10 @@ public interface SalesforceEndpointBuilderFactory {
             return this;
         }
         /**
-         * Use raw payload String for request and response (either JSON or XML
-         * depending on format), instead of DTOs, false by default.
+         * Use raw payload for request and response, instead of DTOs, false by
+         * default. The REST, apexCall and composite operations return the
+         * response as is, as an InputStream, and the streaming subscribe
+         * consumer gives the message as JSON text.
          * 
          * The option will be converted to a <code>boolean</code> type.
          * 

Reply via email to