You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by je...@apache.org on 2025/07/30 19:57:20 UTC

(camel) branch main updated: CAMEL-22244: camel-salesforce: Add support for multipart SObject creation with binary field detection

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

jeremyross 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 3711604973d CAMEL-22244: camel-salesforce: Add support for multipart SObject creation with binary field detection
3711604973d is described below

commit 3711604973d76254310e4837fb4ac50ae9358329
Author: Jeremy Ross <je...@gmail.com>
AuthorDate: Sun Jul 27 13:20:08 2025 -0500

    CAMEL-22244: camel-salesforce: Add support for multipart SObject creation with binary field detection
---
 .../src/main/resources/sobject-pojo.vm             |  33 +++-
 .../src/main/docs/salesforce-component.adoc        | 157 +++++++++++++++-
 .../internal/client/DefaultRestClient.java         | 201 ++++++++++++++++++++
 .../salesforce/internal/client/RestClient.java     |  27 +++
 .../internal/processor/AbstractRestProcessor.java  |  72 +++++++-
 .../component/salesforce/RestApiManualIT.java      |  48 ++++-
 .../salesforce/dto/generated/ContentVersion.java   |  47 +++--
 .../salesforce/dto/generated/Document.java         |  98 ++++++++--
 .../component/salesforce/dto/generated/Folder.java | 202 +++++++++++++++++++++
 .../dto/generated/QueryRecordsFolder.java          |  30 +++
 10 files changed, 867 insertions(+), 48 deletions(-)

diff --git a/components/camel-salesforce/camel-salesforce-codegen/src/main/resources/sobject-pojo.vm b/components/camel-salesforce/camel-salesforce-codegen/src/main/resources/sobject-pojo.vm
index 80cf5761165..5a7c1a38391 100644
--- a/components/camel-salesforce/camel-salesforce-codegen/src/main/resources/sobject-pojo.vm
+++ b/components/camel-salesforce/camel-salesforce-codegen/src/main/resources/sobject-pojo.vm
@@ -23,10 +23,20 @@ package $packageName;
 import java.util.ArrayList;
 import java.util.List;
 
-import jakarta.annotation.Generated;
-
 #set ( $hasPicklists = $utility.hasPicklists($desc) )
 #set ( $hasMultiSelectPicklists = $utility.hasMultiSelectPicklists($desc) )
+#set ( $hasBlobFields = false )
+#foreach ( $field in $desc.Fields )
+#if ( $utility.isBlobField($field) )
+#set ( $hasBlobFields = true )
+#break
+#end
+#end
+#if ( $hasBlobFields )
+import java.io.InputStream;
+#end
+import jakarta.annotation.Generated;
+
 #if ( $hasPicklists && !$useStringsForPicklists )
 #end
 #if ( $hasMultiSelectPicklists )
@@ -52,6 +62,9 @@ import com.fasterxml.jackson.annotation.JsonProperty;
 import com.fasterxml.jackson.annotation.JsonSubTypes;
 import com.fasterxml.jackson.annotation.JsonTypeInfo;
 import com.fasterxml.jackson.databind.annotation.JsonTypeResolver;
+#if ( $hasBlobFields )
+import com.fasterxml.jackson.annotation.JsonIgnore;
+#end
 import org.apache.camel.component.salesforce.api.utils.AsNestedPropertyResolver;
 #if ( $hasMultiSelectPicklists )
 import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
@@ -77,6 +90,7 @@ public class $desc.Name extends AbstractDescribedSObjectBase {
 #if ( ($utility.notBaseField($field.Name)) && ($fieldType) )
 #set ( $fieldName = $field.Name )
 #set ( $isMultiSelectPicklist = $utility.isMultiSelectPicklist($field) )
+#set ( $isBlobField = $utility.isBlobField($field) )
 #set ( $propertyName = $fieldName )
     private $fieldType $propertyName;
 
@@ -104,6 +118,21 @@ public class $desc.Name extends AbstractDescribedSObjectBase {
         this.$propertyName = $propertyName;
     }
 
+#if ( $isBlobField )
+    @JsonIgnore
+    private InputStream ${propertyName}Binary;
+
+    @JsonIgnore
+    public InputStream get${propertyName}Binary() {
+        return this.${propertyName}Binary;
+    }
+
+    @JsonIgnore
+    public void set${propertyName}Binary(InputStream ${propertyName}Binary) {
+        this.${propertyName}Binary = ${propertyName}Binary;
+    }
+
+#end
 #end
 #if ( $field.type == "reference" && $utility.notNull($field.relationshipName) )## IS LOOKUP $hasDescription
 #if ( $field.getReferenceTo().size() > 1 )
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 b9c50e3a38f..be5b55bd808 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
@@ -457,6 +457,10 @@ Creates a record in salesforce.
 
 Type: `CreateSObjectResult`
 
+*Multipart Support*
+
+When creating records with blob/binary fields, you can use the automatic https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/dome_sobject_insert_update_blob.htm[multipart encoding] feature by populating binary fields in your DTO. See <<BinaryFields,Binary Fields and Multipart Requests>> for details.
+
 [[updateSObject]]
 ===== Update SObject
 
@@ -472,6 +476,10 @@ Updates a record in salesforce.
 | `sObjectId` | `String` | Id of record to update. Only used if Camel cannot determine from Body. | | If Body is a `String`
 |===
 
+*Multipart Support*
+
+When updating records with blob/binary fields, you can use the automatic https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/dome_sobject_insert_update_blob.htm[multipart encoding] feature by populating binary fields in your DTO. See <<BinaryFields,Binary Fields and Multipart Requests>> for details.
+
 [[upsertSObject]]
 ===== Upsert SObject
 
@@ -1020,6 +1028,109 @@ Gets the definition of a Platform Event in JSON format. Other types of events su
 
 Type: `InputStream`
 
+[[BinaryFields]]
+==== Binary Fields and Multipart Requests
+
+The Salesforce component provides automatic support for handling binary fields (blob fields) in SObjects without requiring manual base64 encoding. This feature works with both `createSObject` and `updateSObject` operations.
+
+===== Binary Field Pattern
+
+For any SObject with blob fields, you can use binary fields that follow the naming pattern `pass:[***]Binary`, where `pass:[***]` is the original field name. For example:
+
+* `VersionData` field → `VersionDataBinary` field
+* `Body` field → `BodyBinary` field
+* `ContentData` field → `ContentDataBinary` field
+
+===== Automatic Multipart Detection
+
+When you populate a `pass:[***]Binary` field with an `InputStream`, the component automatically detects this and switches to multipart encoding for the request. This means that the binary data is sent directly as a stream, avoiding the overhead of base64 encoding.:
+
+===== Usage Examples
+
+*Creating a ContentVersion with binary data:*
+
+[source,java]
+----
+// Create ContentVersion with binary field
+ContentVersion cv = new ContentVersion();
+cv.setTitle("My Document");
+cv.setPathOnClient("document.pdf");
+
+// Use binary field instead of base64 encoded string
+InputStream fileData = new FileInputStream("document.pdf");
+cv.setVersionDataBinary(fileData);  // Automatically triggers multipart request
+
+// Send to Salesforce
+template.requestBody("salesforce:createSObject", cv);
+----
+
+*Updating a Document with binary content:*
+
+[source,java]
+----
+// Update Document with binary field
+Document doc = new Document();
+doc.setId("01234567890123456");
+doc.setName("Updated Document");
+
+// Use binary field for efficient upload
+InputStream content = getClass().getResourceAsStream("/updated-content.pdf");
+doc.setBodyBinary(content);  // Automatically triggers multipart request
+
+// Send to Salesforce
+template.requestBody("salesforce:updateSObject", doc);
+----
+
+===== DTO Generation
+
+When using the <<MavenPlugin,Camel Salesforce Maven Plugin>>, binary fields are automatically generated for blob fields:
+
+[source,java]
+----
+public class ContentVersion extends AbstractSObjectBase {
+    
+    // Standard base64 field (for backward compatibility)
+    private String VersionData;
+    
+    // Generated binary field (new efficient approach)
+    @JsonIgnore
+    private InputStream VersionDataBinary;
+    
+    // Getters and setters for both fields...
+}
+----
+
+===== Field Priority
+
+When both the standard field and binary field are populated:
+
+* **Binary field takes precedence** - The `pass:[***]Binary` field is used and a multipart request is made
+* **Standard field is ignored** - The base64 string field is not sent in the multipart request
+* **Automatic cleanup** - Binary field names are automatically removed from the JSON payload
+
+===== Performance Benefits
+
+Using binary fields provides significant performance improvements:
+
+* **No base64 encoding** required on the client side
+* **Direct streaming** of binary data to Salesforce
+* **Reduced memory usage** for large files
+* **Faster upload times** especially for large documents
+* **Higher salesforce limits** for multipart requests compared to base64 strings. Salesforce limits base64 strings to 50 MB of text data or 37.5 MB of base64–encoded data. Multipart uploads can handle 2 GB for ContentVersion records, and 500 MB for all other objects.
+
+===== Backward Compatibility
+
+The existing base64 string approach continues to work:
+
+[source,java]
+----
+// Still supported - base64 approach
+ContentVersion cv = new ContentVersion();
+cv.setVersionData(base64EncodedString);  // Uses standard JSON request
+----
+
+However, using binary fields is recommended for better performance when working with binary data.
+
 [[ApexRESTAPI]]
 ==== Apex REST API
 
@@ -2034,7 +2145,9 @@ The response is:
 
 === Uploading a document to a ContentWorkspace
 
-Create the ContentVersion in Java, using a Processor instance:
+==== Using Binary Fields (Recommended)
+
+For efficient document uploads without base64 encoding overhead, use the binary field approach:
 
 [source,java]
 ----
@@ -2047,10 +2160,10 @@ public class ContentProcessor implements Processor {
         cv.setFirstPublishLocationId(cw.getId());
         cv.setTitle("test document");
         cv.setPathOnClient("test_doc.html");
-        byte[] document = message.getBody(byte[].class);
-        ObjectMapper mapper = new ObjectMapper();
-        String enc = mapper.convertValue(document, String.class);
-        cv.setVersionDataUrl(enc);
+        
+        // Use binary field for efficient upload - no base64 encoding needed
+        InputStream document = message.getBody(InputStream.class);
+        cv.setVersionDataBinary(document);  // Automatically triggers multipart request
         message.setBody(cv);
     }
 
@@ -2067,11 +2180,41 @@ Give the output from the processor to the Salesforce component:
 [source,java]
 ----
 from("file:///home/camel/library")
-    .to(new ContentProcessor())     // convert bytes from the file into a ContentVersion SObject
-                                    // for the salesforce component
+    .to(new ContentProcessor())     // convert file stream into a ContentVersion SObject
+                                    // with binary field for efficient upload
     .to("salesforce:createSObject");
 ----
 
+==== Using Base64 Encoding (Legacy)
+
+The traditional base64 encoding approach is still supported for backward compatibility:
+
+[source,java]
+----
+public class ContentProcessor implements Processor {
+    public void process(Exchange exchange) throws Exception {
+        Message message = exchange.getIn();
+
+        ContentVersion cv = new ContentVersion();
+        ContentWorkspace cw = getWorkspace(exchange);
+        cv.setFirstPublishLocationId(cw.getId());
+        cv.setTitle("test document");
+        cv.setPathOnClient("test_doc.html");
+        byte[] document = message.getBody(byte[].class);
+        ObjectMapper mapper = new ObjectMapper();
+        String enc = mapper.convertValue(document, String.class);
+        cv.setVersionData(enc);  // Uses base64 encoding and standard JSON request
+        message.setBody(cv);
+    }
+
+    protected ContentWorkspace getWorkSpace(Exchange exchange) {
+        // Look up the content workspace somehow, maybe use enrich() to add it to a
+        // header that can be extracted here
+        ....
+    }
+}
+----
+
 === Generating SOQL query strings
 
 `org.apache.camel.component.salesforce.api.utils.QueryHelper` contains helper
diff --git a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/client/DefaultRestClient.java b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/client/DefaultRestClient.java
index c88de491804..654b9ab88d7 100644
--- a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/client/DefaultRestClient.java
+++ b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/client/DefaultRestClient.java
@@ -16,16 +16,22 @@
  */
 package org.apache.camel.component.salesforce.internal.client;
 
+import java.io.ByteArrayInputStream;
 import java.io.InputStream;
 import java.io.UnsupportedEncodingException;
 import java.net.URISyntaxException;
 import java.net.URLEncoder;
 import java.nio.charset.StandardCharsets;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
 import org.apache.camel.component.salesforce.SalesforceHttpClient;
 import org.apache.camel.component.salesforce.SalesforceLoginConfig;
 import org.apache.camel.component.salesforce.api.SalesforceException;
@@ -34,9 +40,12 @@ import org.apache.camel.component.salesforce.internal.SalesforceSession;
 import org.apache.camel.util.ObjectHelper;
 import org.apache.camel.util.URISupport;
 import org.eclipse.jetty.client.InputStreamRequestContent;
+import org.eclipse.jetty.client.MultiPartRequestContent;
 import org.eclipse.jetty.client.Request;
+import org.eclipse.jetty.http.HttpFields;
 import org.eclipse.jetty.http.HttpHeader;
 import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.MultiPart;
 
 public class DefaultRestClient extends AbstractClientBase implements RestClient {
 
@@ -169,6 +178,127 @@ public class DefaultRestClient extends AbstractClientBase implements RestClient
         doHttpRequest(post, new DelegatingClientCallback(callback));
     }
 
+    @Override
+    public void createSObjectMultipart(
+            String sObjectName, Object sObjectDto, InputStream sObject,
+            Map<String, List<String>> headers, ResponseCallback callback) {
+        try {
+            final Request post = getRequest(HttpMethod.POST, sobjectsUrl(sObjectName), headers);
+
+            setAccessToken(post);
+
+            MultiPartRequestContent multipartContent = new MultiPartRequestContent();
+
+            ObjectMapper objectMapper = new ObjectMapper();
+            JsonNode jsonNode = objectMapper.readTree(sObject);
+
+            if (jsonNode.isObject()) {
+                ObjectNode objectNode = (ObjectNode) jsonNode;
+                ObjectNode cleanJson = objectNode.deepCopy();
+
+                Map<String, InputStream> binaryFields = getBinaryFieldMap(sObjectDto);
+
+                if (!binaryFields.isEmpty()) {
+
+                    // Remove binary field names from JSON first
+                    for (String fieldName : binaryFields.keySet()) {
+                        cleanJson.remove(fieldName);
+                    }
+
+                    // non-binary json data must be the first part of the multipart request
+                    String cleanJsonString = objectMapper.writeValueAsString(cleanJson);
+                    multipartContent.addPart(new MultiPart.ContentSourcePart(
+                            "entity", null,
+                            HttpFields.build().add(HttpHeader.CONTENT_TYPE, APPLICATION_JSON_UTF8),
+                            new InputStreamRequestContent(
+                                    new ByteArrayInputStream(cleanJsonString.getBytes(StandardCharsets.UTF_8)))));
+
+                    // Then add binary fields as subsequent parts
+                    for (Map.Entry<String, InputStream> entry : binaryFields.entrySet()) {
+                        String fieldName = entry.getKey();
+                        InputStream binaryData = entry.getValue();
+
+                        String contentType = inferContentType(fieldName, sObjectName);
+                        multipartContent.addPart(new MultiPart.ContentSourcePart(
+                                fieldName, "temp-file-name.doc",
+                                HttpFields.build().add(HttpHeader.CONTENT_TYPE, contentType),
+                                new InputStreamRequestContent(binaryData)));
+                    }
+                } else {
+                    // No multipart data found - this shouldn't happen as processor should handle this case
+                    callback.onResponse(null, Collections.emptyMap(),
+                            new SalesforceException("createSObjectMultipart called but no binary fields found", null));
+                    return;
+                }
+            } else {
+                // If not a JSON object, send as-is
+                multipartContent.addPart(new MultiPart.ContentSourcePart(
+                        "entity", null,
+                        HttpFields.build().add(HttpHeader.CONTENT_TYPE, APPLICATION_JSON_UTF8),
+                        new InputStreamRequestContent(sObject)));
+            }
+
+            multipartContent.close();
+            post.body(multipartContent);
+
+            doHttpRequest(post, new DelegatingClientCallback(callback));
+        } catch (Exception e) {
+            // If JSON parsing fails, fall back to regular processing
+            callback.onResponse(null, Collections.emptyMap(),
+                    new SalesforceException("Failed to process multipart request: " + e.getMessage(), e));
+        }
+    }
+
+    private Map<String, InputStream> getBinaryFieldMap(Object sObject) {
+        Map<String, InputStream> binaryFields = new HashMap<>();
+
+        if (sObject == null) {
+            return binaryFields;
+        }
+
+        Class<?> clazz = sObject.getClass();
+        java.lang.reflect.Field[] fields = clazz.getDeclaredFields();
+
+        for (java.lang.reflect.Field field : fields) {
+            String fieldName = field.getName();
+
+            if (fieldName.endsWith("Binary") && field.isAnnotationPresent(JsonIgnore.class)) {
+                try {
+                    field.setAccessible(true);
+                    Object value = field.get(sObject);
+                    if (value instanceof InputStream) {
+                        String originalFieldName = fieldName.replace("Binary", "");
+                        binaryFields.put(originalFieldName, (InputStream) value);
+                    }
+                } catch (Exception e) {
+                    // Skip inaccessible fields
+                }
+            }
+        }
+
+        return binaryFields;
+    }
+
+    private String inferContentType(String fieldName, String sObjectType) {
+        // Infer content type based on field name and object type
+        switch (fieldName.toLowerCase()) {
+            case "body":
+                if ("Document".equalsIgnoreCase(sObjectType)) {
+                    return "application/octet-stream";
+                } else if ("Attachment".equalsIgnoreCase(sObjectType)) {
+                    return "application/octet-stream";
+                }
+                break;
+            case "versiondata":
+                return "application/octet-stream";
+            case "contentdata":
+                return "application/octet-stream";
+            default:
+                return "application/octet-stream";
+        }
+        return "application/octet-stream";
+    }
+
     @Override
     public void updateSObject(
             String sObjectName, String id, InputStream sObject, Map<String, List<String>> headers, ResponseCallback callback) {
@@ -183,6 +313,77 @@ public class DefaultRestClient extends AbstractClientBase implements RestClient
         doHttpRequest(patch, new DelegatingClientCallback(callback));
     }
 
+    @Override
+    public void updateSObjectMultipart(
+            String sObjectName, String id, Object sObjectDto, InputStream sObject,
+            Map<String, List<String>> headers, ResponseCallback callback) {
+        try {
+            final Request patch = getRequest("PATCH", sobjectsUrl(sObjectName + "/" + id), headers);
+
+            setAccessToken(patch);
+
+            MultiPartRequestContent multipartContent = new MultiPartRequestContent();
+
+            ObjectMapper objectMapper = new ObjectMapper();
+            JsonNode jsonNode = objectMapper.readTree(sObject);
+
+            if (jsonNode.isObject()) {
+                ObjectNode objectNode = (ObjectNode) jsonNode;
+                ObjectNode cleanJson = objectNode.deepCopy();
+
+                Map<String, InputStream> binaryFields = getBinaryFieldMap(sObjectDto);
+
+                if (!binaryFields.isEmpty()) {
+
+                    // Remove binary field names from JSON first
+                    for (String fieldName : binaryFields.keySet()) {
+                        cleanJson.remove(fieldName);
+                    }
+
+                    // non-binary json data must be the first part of the multipart request
+                    String cleanJsonString = objectMapper.writeValueAsString(cleanJson);
+                    multipartContent.addPart(new MultiPart.ContentSourcePart(
+                            "entity", null,
+                            HttpFields.build().add(HttpHeader.CONTENT_TYPE, APPLICATION_JSON_UTF8),
+                            new InputStreamRequestContent(
+                                    new ByteArrayInputStream(cleanJsonString.getBytes(StandardCharsets.UTF_8)))));
+
+                    // Then add binary fields as subsequent parts
+                    for (Map.Entry<String, InputStream> entry : binaryFields.entrySet()) {
+                        String fieldName = entry.getKey();
+                        InputStream binaryData = entry.getValue();
+
+                        String contentType = inferContentType(fieldName, sObjectName);
+                        multipartContent.addPart(new MultiPart.ContentSourcePart(
+                                fieldName, "temp-file-name.doc",
+                                HttpFields.build().add(HttpHeader.CONTENT_TYPE, contentType),
+                                new InputStreamRequestContent(binaryData)));
+                    }
+                } else {
+                    // No multipart data found - this shouldn't happen as processor should handle this case
+                    callback.onResponse(null, Collections.emptyMap(),
+                            new SalesforceException("updateSObjectMultipart called but no binary fields found", null));
+                    return;
+                }
+            } else {
+                // If not a JSON object, send as-is
+                multipartContent.addPart(new MultiPart.ContentSourcePart(
+                        "entity", null,
+                        HttpFields.build().add(HttpHeader.CONTENT_TYPE, APPLICATION_JSON_UTF8),
+                        new InputStreamRequestContent(sObject)));
+            }
+
+            multipartContent.close();
+            patch.body(multipartContent);
+
+            doHttpRequest(patch, new DelegatingClientCallback(callback));
+        } catch (Exception e) {
+            // If JSON parsing fails, fall back to regular processing
+            callback.onResponse(null, Collections.emptyMap(),
+                    new SalesforceException("Failed to process multipart update request: " + e.getMessage(), e));
+        }
+    }
+
     @Override
     public void deleteSObject(String sObjectName, String id, Map<String, List<String>> headers, ResponseCallback callback) {
         final Request delete = getRequest(HttpMethod.DELETE, sobjectsUrl(sObjectName + "/" + id), headers);
diff --git a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/client/RestClient.java b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/client/RestClient.java
index c8bf525ff1a..4d67b25f5d0 100644
--- a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/client/RestClient.java
+++ b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/client/RestClient.java
@@ -92,6 +92,19 @@ public interface RestClient {
      */
     void createSObject(String sObjectName, InputStream sObject, Map<String, List<String>> headers, ResponseCallback callback);
 
+    /**
+     * Creates multipart SObject using multipart/form-data for blob fields, with automatic binary field detection.
+     *
+     * @param sObjectName specified object name
+     * @param sObjectDto  original DTO object for binary field detection (AbstractSObjectBase or subclass)
+     * @param sObject     request entity as JSON
+     * @param headers     additional HTTP headers to send
+     * @param callback    {@link ResponseCallback} to handle response or exception
+     */
+    void createSObjectMultipart(
+            String sObjectName, Object sObjectDto, InputStream sObject,
+            Map<String, List<String>> headers, ResponseCallback callback);
+
     /**
      * Updates a record for the specified object ID.
      *
@@ -104,6 +117,20 @@ public interface RestClient {
     void updateSObject(
             String sObjectName, String id, InputStream sObject, Map<String, List<String>> headers, ResponseCallback callback);
 
+    /**
+     * Updates multipart SObject using multipart/form-data for blob fields, with automatic binary field detection.
+     *
+     * @param sObjectName specified object name
+     * @param id          object id
+     * @param sObjectDto  original DTO object for binary field detection (AbstractSObjectBase or subclass)
+     * @param sObject     request entity as JSON
+     * @param headers     additional HTTP headers to send
+     * @param callback    {@link ResponseCallback} to handle response or exception
+     */
+    void updateSObjectMultipart(
+            String sObjectName, String id, Object sObjectDto, InputStream sObject,
+            Map<String, List<String>> headers, ResponseCallback callback);
+
     /**
      * Deletes a record for the specified object ID.
      *
diff --git a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/processor/AbstractRestProcessor.java b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/processor/AbstractRestProcessor.java
index 724386879a2..254ade8f7da 100644
--- a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/processor/AbstractRestProcessor.java
+++ b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/processor/AbstractRestProcessor.java
@@ -30,6 +30,7 @@ import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 import java.util.stream.StreamSupport;
 
+import com.fasterxml.jackson.annotation.JsonIgnore;
 import org.apache.camel.AsyncCallback;
 import org.apache.camel.Exchange;
 import org.apache.camel.Message;
@@ -378,8 +379,44 @@ public abstract class AbstractRestProcessor extends AbstractSalesforceProcessor
     private void processCreateSobject(final Exchange exchange, final AsyncCallback callback) throws SalesforceException {
         String sObjectName = determineSObjectName(exchange);
 
-        restClient.createSObject(sObjectName, getRequestStream(exchange), determineHeaders(exchange),
-                processWithResponseCallback(exchange, callback));
+        Message in = exchange.getIn();
+        AbstractSObjectBase sObjectBase = in.getBody(AbstractSObjectBase.class);
+
+        if (sObjectBase != null && detectBinaryFields(sObjectBase)) {
+            restClient.createSObjectMultipart(sObjectName, sObjectBase, getRequestStream(exchange),
+                    determineHeaders(exchange),
+                    processWithResponseCallback(exchange, callback));
+        } else {
+            restClient.createSObject(sObjectName, getRequestStream(exchange), determineHeaders(exchange),
+                    processWithResponseCallback(exchange, callback));
+        }
+    }
+
+    private boolean detectBinaryFields(AbstractSObjectBase sObject) {
+        if (sObject == null) {
+            return false;
+        }
+
+        Class<?> clazz = sObject.getClass();
+        java.lang.reflect.Field[] fields = clazz.getDeclaredFields();
+
+        for (java.lang.reflect.Field field : fields) {
+            String fieldName = field.getName();
+
+            if (fieldName.endsWith("Binary") && field.isAnnotationPresent(JsonIgnore.class)) {
+                try {
+                    field.setAccessible(true);
+                    Object value = field.get(sObject);
+                    if (value instanceof InputStream) {
+                        return true;
+                    }
+                } catch (Exception e) {
+                    // Skip inaccessible fields
+                }
+            }
+        }
+
+        return false;
     }
 
     private void processUpdateSobject(final Exchange exchange, final AsyncCallback callback) throws SalesforceException {
@@ -393,14 +430,29 @@ public abstract class AbstractRestProcessor extends AbstractSalesforceProcessor
         }
 
         final String finalsObjectId = sObjectId;
-        restClient.updateSObject(sObjectName, sObjectId, getRequestStream(exchange), determineHeaders(exchange),
-                new RestClient.ResponseCallback() {
-                    @Override
-                    public void onResponse(InputStream response, Map<String, String> headers, SalesforceException exception) {
-                        restoreFields(exchange, sObjectBase, finalsObjectId, null, null);
-                        processResponse(exchange, response, headers, exception, callback);
-                    }
-                });
+
+        if (sObjectBase != null && detectBinaryFields(sObjectBase)) {
+            restClient.updateSObjectMultipart(sObjectName, sObjectId, sObjectBase, getRequestStream(exchange),
+                    determineHeaders(exchange),
+                    new RestClient.ResponseCallback() {
+                        @Override
+                        public void onResponse(
+                                InputStream response, Map<String, String> headers, SalesforceException exception) {
+                            restoreFields(exchange, sObjectBase, finalsObjectId, null, null);
+                            processResponse(exchange, response, headers, exception, callback);
+                        }
+                    });
+        } else {
+            restClient.updateSObject(sObjectName, sObjectId, getRequestStream(exchange), determineHeaders(exchange),
+                    new RestClient.ResponseCallback() {
+                        @Override
+                        public void onResponse(
+                                InputStream response, Map<String, String> headers, SalesforceException exception) {
+                            restoreFields(exchange, sObjectBase, finalsObjectId, null, null);
+                            processResponse(exchange, response, headers, exception, callback);
+                        }
+                    });
+        }
     }
 
     private void processDeleteSobject(final Exchange exchange, final AsyncCallback callback) throws SalesforceException {
diff --git a/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/RestApiManualIT.java b/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/RestApiManualIT.java
index 0866db4fae6..783784e1319 100644
--- a/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/RestApiManualIT.java
+++ b/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/RestApiManualIT.java
@@ -51,10 +51,12 @@ import org.apache.camel.component.salesforce.dto.generated.Account;
 import org.apache.camel.component.salesforce.dto.generated.Contact;
 import org.apache.camel.component.salesforce.dto.generated.ContentVersion;
 import org.apache.camel.component.salesforce.dto.generated.Document;
+import org.apache.camel.component.salesforce.dto.generated.Folder;
 import org.apache.camel.component.salesforce.dto.generated.Line_Item__c;
 import org.apache.camel.component.salesforce.dto.generated.Merchandise__c;
 import org.apache.camel.component.salesforce.dto.generated.QueryRecordsAccount;
 import org.apache.camel.component.salesforce.dto.generated.QueryRecordsContact;
+import org.apache.camel.component.salesforce.dto.generated.QueryRecordsFolder;
 import org.apache.camel.component.salesforce.dto.generated.QueryRecordsLine_Item__c;
 import org.apache.camel.component.salesforce.dto.generated.Task;
 import org.apache.camel.component.salesforce.dto.generated.User;
@@ -280,6 +282,50 @@ public class RestApiManualIT extends AbstractSalesforceTestBase {
         assertNotNull(template().requestBody("salesforce:deleteSObject?sObjectName=Merchandise__c", result.getId()));
     }
 
+    @Test
+    public void testCreateMultipart() {
+        final ContentVersion cv = new ContentVersion();
+        cv.setPathOnClient("camel-test-doc.pdf");
+        cv.setVersionDataBinary(getClass().getClassLoader().getResourceAsStream("camel-test-doc.pdf"));
+        final CreateSObjectResult result
+                = template.requestBody("salesforce:createSObject?sObjectName=ContentVersion",
+                        cv,
+                        CreateSObjectResult.class);
+        assertTrue(result.getSuccess());
+    }
+
+    @Test
+    public void testUpdateMultipart() {
+        final QueryRecordsFolder queryResult = template.requestBody("salesforce:query" +
+                                                                    "?sObjectQuery=SELECT Id FROM Folder WHERE Name = 'Test Documents'"
+                                                                    +
+                                                                    "&sObjectName=QueryRecordsFolder",
+                null, QueryRecordsFolder.class);
+        final Folder folder = queryResult.getRecords().get(0);
+
+        // Create a Document
+        final Document doc = new Document();
+        doc.setFolderId(folder.getId());
+        doc.setName("camel-test-doc.pdf");
+        doc.setBodyBinary(getClass().getClassLoader().getResourceAsStream("camel-test-doc.pdf"));
+        final CreateSObjectResult createResult = template.requestBody(
+                "salesforce:createSObject?sObjectName=Document",
+                doc,
+                CreateSObjectResult.class);
+        assertTrue(createResult.getSuccess());
+        assertNotNull(createResult.getId());
+
+        // Update the Document (e.g., change the name)
+        final Document updateDoc = new Document();
+        updateDoc.setId(createResult.getId());
+        updateDoc.setName("camel-test-doc-updated.pdf");
+        updateDoc.setBodyBinary(getClass().getClassLoader().getResourceAsStream("camel-test-doc.pdf"));
+        final Object updateResult = template.requestBody(
+                "salesforce:updateSObject?sObjectName=Document",
+                updateDoc);
+        assertNotNull(updateResult);
+    }
+
     @Test
     public void testRelationshipCreateDelete() throws Exception {
         final Account account = new Account();
@@ -476,7 +522,7 @@ public class RestApiManualIT extends AbstractSalesforceTestBase {
         ObjectMapper mapper = new ObjectMapper();
         String enc = mapper.convertValue(bytes, String.class);
         ContentVersion cv = new ContentVersion();
-        cv.setVersionDataUrl(enc);
+        cv.setVersionData(enc);
         cv.setPathOnClient("camel-test-doc.pdf");
         cv.setTitle("Camel Test Doc");
         final CreateSObjectResult result = template.requestBody("salesforce:createSObject", cv, CreateSObjectResult.class);
diff --git a/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/dto/generated/ContentVersion.java b/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/dto/generated/ContentVersion.java
index 1ff87ba22ad..bd78c320f1a 100644
--- a/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/dto/generated/ContentVersion.java
+++ b/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/dto/generated/ContentVersion.java
@@ -19,25 +19,20 @@
  */
 package org.apache.camel.component.salesforce.dto.generated;
 
+import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.List;
 
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonSubTypes;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.fasterxml.jackson.databind.annotation.JsonTypeResolver;
 import jakarta.annotation.Generated;
-
 import org.apache.camel.component.salesforce.api.dto.AbstractDescribedSObjectBase;
-import org.apache.camel.component.salesforce.api.dto.Attributes;
-import org.apache.camel.component.salesforce.api.dto.ChildRelationShip;
-import org.apache.camel.component.salesforce.api.dto.InfoUrls;
-import org.apache.camel.component.salesforce.api.dto.NamedLayoutInfo;
-import org.apache.camel.component.salesforce.api.dto.RecordTypeInfo;
 import org.apache.camel.component.salesforce.api.dto.SObjectDescription;
 import org.apache.camel.component.salesforce.api.dto.SObjectDescriptionUrls;
 import org.apache.camel.component.salesforce.api.dto.SObjectField;
-
-import com.fasterxml.jackson.annotation.JsonProperty;
-import com.fasterxml.jackson.annotation.JsonSubTypes;
-import com.fasterxml.jackson.annotation.JsonTypeInfo;
-import com.fasterxml.jackson.databind.annotation.JsonTypeResolver;
 import org.apache.camel.component.salesforce.api.utils.AsNestedPropertyResolver;
 
 /**
@@ -370,14 +365,40 @@ public class ContentVersion extends AbstractDescribedSObjectBase {
         this.PublishStatus = PublishStatus;
     }
 
-    private String VersionDataUrl;
+
+    private String VersionData;
 
     @JsonProperty("VersionData")
+    public String getVersionData() {
+        return this.VersionData;
+    }
+
+    @JsonProperty("VersionData")
+    public void setVersionData(String VersionData) {
+        this.VersionData = VersionData;
+    }
+
+    @JsonIgnore
+    private InputStream VersionDataBinary;
+
+    @JsonIgnore
+    public InputStream getVersionDataBinary() {
+        return this.VersionDataBinary;
+    }
+
+    @JsonIgnore
+    public void setVersionDataBinary(InputStream VersionDataBinary) {
+        this.VersionDataBinary = VersionDataBinary;
+    }
+
+    @JsonProperty("VersionDataUrl")
     public String getVersionDataUrl() {
         return this.VersionDataUrl;
     }
 
-    @JsonProperty("VersionData")
+    private String VersionDataUrl;
+
+    @JsonProperty("VersionDataUrl")
     public void setVersionDataUrl(String VersionDataUrl) {
         this.VersionDataUrl = VersionDataUrl;
     }
diff --git a/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/dto/generated/Document.java b/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/dto/generated/Document.java
index 309402b3bbd..7ce44aa60cd 100644
--- a/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/dto/generated/Document.java
+++ b/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/dto/generated/Document.java
@@ -16,18 +16,26 @@
  */
 package org.apache.camel.component.salesforce.dto.generated;
 
+import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.List;
 
+import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonSubTypes;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.fasterxml.jackson.databind.annotation.JsonTypeResolver;
+import jakarta.annotation.Generated;
 import org.apache.camel.component.salesforce.api.dto.AbstractDescribedSObjectBase;
 import org.apache.camel.component.salesforce.api.dto.SObjectDescription;
 import org.apache.camel.component.salesforce.api.dto.SObjectDescriptionUrls;
 import org.apache.camel.component.salesforce.api.dto.SObjectField;
+import org.apache.camel.component.salesforce.api.utils.AsNestedPropertyResolver;
 
 /**
  * Salesforce DTO for SObject Document
  */
+@Generated("org.apache.camel.maven.CamelSalesforceMojo")
 public class Document extends AbstractDescribedSObjectBase {
 
     public Document() {
@@ -48,6 +56,22 @@ public class Document extends AbstractDescribedSObjectBase {
         this.FolderId = FolderId;
     }
 
+    @JsonTypeResolver(AsNestedPropertyResolver.class)
+    @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "attributes.type",
+            defaultImpl = AbstractDescribedSObjectBase.class)
+    @JsonSubTypes({
+    })
+    private AbstractDescribedSObjectBase Folder;
+
+    @JsonProperty("Folder")
+    public AbstractDescribedSObjectBase getFolder() {
+        return this.Folder;
+    }
+
+    @JsonProperty("Folder")
+    public void setFolder(AbstractDescribedSObjectBase Folder) {
+        this.Folder = Folder;
+    }
     private String DeveloperName;
 
     @JsonProperty("DeveloperName")
@@ -120,17 +144,29 @@ public class Document extends AbstractDescribedSObjectBase {
         this.BodyLength = BodyLength;
     }
 
-    // blob field url, use getBlobField to get the content
-    private String BodyUrl;
+    private String Body;
 
     @JsonProperty("Body")
-    public String getBodyUrl() {
-        return this.BodyUrl;
+    public String getBody() {
+        return this.Body;
     }
 
     @JsonProperty("Body")
-    public void setBodyUrl(String BodyUrl) {
-        this.BodyUrl = BodyUrl;
+    public void setBody(String Body) {
+        this.Body = Body;
+    }
+
+    @JsonIgnore
+    private InputStream bodyBinary;
+
+    @JsonIgnore
+    public InputStream getBodyBinary() {
+        return this.bodyBinary;
+    }
+
+    @JsonIgnore
+    public void setBodyBinary(InputStream bodyBinary) {
+        this.bodyBinary = bodyBinary;
     }
 
     private String Url;
@@ -193,6 +229,39 @@ public class Document extends AbstractDescribedSObjectBase {
         this.AuthorId = AuthorId;
     }
 
+    private AbstractDescribedSObjectBase Author;
+
+    @JsonProperty("Author")
+    public AbstractDescribedSObjectBase getAuthor() {
+        return this.Author;
+    }
+
+    @JsonProperty("Author")
+    public void setAuthor(AbstractDescribedSObjectBase Author) {
+        this.Author = Author;
+    }
+    private AbstractDescribedSObjectBase CreatedBy;
+
+    @JsonProperty("CreatedBy")
+    public AbstractDescribedSObjectBase getCreatedBy() {
+        return this.CreatedBy;
+    }
+
+    @JsonProperty("CreatedBy")
+    public void setCreatedBy(AbstractDescribedSObjectBase CreatedBy) {
+        this.CreatedBy = CreatedBy;
+    }
+    private AbstractDescribedSObjectBase LastModifiedBy;
+
+    @JsonProperty("LastModifiedBy")
+    public AbstractDescribedSObjectBase getLastModifiedBy() {
+        return this.LastModifiedBy;
+    }
+
+    @JsonProperty("LastModifiedBy")
+    public void setLastModifiedBy(AbstractDescribedSObjectBase LastModifiedBy) {
+        this.LastModifiedBy = LastModifiedBy;
+    }
     private Boolean IsBodySearchable;
 
     @JsonProperty("IsBodySearchable")
@@ -205,6 +274,7 @@ public class Document extends AbstractDescribedSObjectBase {
         this.IsBodySearchable = IsBodySearchable;
     }
 
+
     @Override
     public final SObjectDescription description() {
         return DESCRIPTION;
@@ -213,6 +283,8 @@ public class Document extends AbstractDescribedSObjectBase {
     private static SObjectDescription createSObjectDescription() {
         final SObjectDescription description = new SObjectDescription();
 
+
+
         final List<SObjectField> fields1 = new ArrayList<>();
         description.setFields(fields1);
 
@@ -258,13 +330,11 @@ public class Document extends AbstractDescribedSObjectBase {
         fields1.add(sObjectField20);
         final SObjectField sObjectField21 = createField("SystemModstamp", "System Modstamp", "datetime", "xsd:dateTime", 0, false, false, false, false, false, false, false);
         fields1.add(sObjectField21);
-        final SObjectField sObjectField22 = createField("IsBodySearchable", "Document Content Searchable", "boolean", "xsd:boolean", 0, false, false, false, false, false, false,
-                                                        false);
+        final SObjectField sObjectField22 = createField("IsBodySearchable", "Document Content Searchable", "boolean", "xsd:boolean", 0, false, false, false, false, false, false, false);
         fields1.add(sObjectField22);
         final SObjectField sObjectField23 = createField("LastViewedDate", "Last Viewed Date", "datetime", "xsd:dateTime", 0, false, true, false, false, false, false, false);
         fields1.add(sObjectField23);
-        final SObjectField sObjectField24 = createField("LastReferencedDate", "Last Referenced Date", "datetime", "xsd:dateTime", 0, false, true, false, false, false, false,
-                                                        false);
+        final SObjectField sObjectField24 = createField("LastReferencedDate", "Last Referenced Date", "datetime", "xsd:dateTime", 0, false, true, false, false, false, false, false);
         fields1.add(sObjectField24);
 
         description.setLabel("Document");
@@ -272,13 +342,11 @@ public class Document extends AbstractDescribedSObjectBase {
         description.setName("Document");
 
         final SObjectDescriptionUrls sObjectDescriptionUrls1 = new SObjectDescriptionUrls();
-        sObjectDescriptionUrls1.setDefaultValues("/services/data/v45.0/sobjects/Document/defaultValues?recordTypeId&fields");
-        sObjectDescriptionUrls1.setDescribe("/services/data/v45.0/sobjects/Document/describe");
-        sObjectDescriptionUrls1.setRowTemplate("/services/data/v45.0/sobjects/Document/{ID}");
-        sObjectDescriptionUrls1.setSobject("/services/data/v45.0/sobjects/Document");
+        sObjectDescriptionUrls1.setDescribe("/services/data/v59.0/sobjects/Document/describe");
+        sObjectDescriptionUrls1.setRowTemplate("/services/data/v59.0/sobjects/Document/{ID}");
+        sObjectDescriptionUrls1.setSobject("/services/data/v59.0/sobjects/Document");
         description.setUrls(sObjectDescriptionUrls1);
 
         return description;
     }
 }
-
diff --git a/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/dto/generated/Folder.java b/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/dto/generated/Folder.java
new file mode 100644
index 00000000000..7c21b55fda5
--- /dev/null
+++ b/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/dto/generated/Folder.java
@@ -0,0 +1,202 @@
+/*
+ * 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.
+ */
+/*
+ * Salesforce DTO generated by camel-salesforce-maven-plugin.
+ */
+package org.apache.camel.component.salesforce.dto.generated;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import jakarta.annotation.Generated;
+import org.apache.camel.component.salesforce.api.dto.AbstractDescribedSObjectBase;
+import org.apache.camel.component.salesforce.api.dto.SObjectDescription;
+import org.apache.camel.component.salesforce.api.dto.SObjectDescriptionUrls;
+import org.apache.camel.component.salesforce.api.dto.SObjectField;
+
+/**
+ * Salesforce DTO for SObject Folder
+ */
+@Generated("org.apache.camel.maven.CamelSalesforceMojo")
+public class Folder extends AbstractDescribedSObjectBase {
+
+    public Folder() {
+        getAttributes().setType("Folder");
+    }
+
+    private static final SObjectDescription DESCRIPTION = createSObjectDescription();
+
+    private String ParentId;
+
+    @JsonProperty("ParentId")
+    public String getParentId() {
+        return this.ParentId;
+    }
+
+    @JsonProperty("ParentId")
+    public void setParentId(String ParentId) {
+        this.ParentId = ParentId;
+    }
+
+    private String DeveloperName;
+
+    @JsonProperty("DeveloperName")
+    public String getDeveloperName() {
+        return this.DeveloperName;
+    }
+
+    @JsonProperty("DeveloperName")
+    public void setDeveloperName(String DeveloperName) {
+        this.DeveloperName = DeveloperName;
+    }
+
+    private String AccessType;
+
+    @JsonProperty("AccessType")
+    public String getAccessType() {
+        return this.AccessType;
+    }
+
+    @JsonProperty("AccessType")
+    public void setAccessType(String AccessType) {
+        this.AccessType = AccessType;
+    }
+
+    private Boolean IsReadonly;
+
+    @JsonProperty("IsReadonly")
+    public Boolean getIsReadonly() {
+        return this.IsReadonly;
+    }
+
+    @JsonProperty("IsReadonly")
+    public void setIsReadonly(Boolean IsReadonly) {
+        this.IsReadonly = IsReadonly;
+    }
+
+    private String Type;
+
+    @JsonProperty("Type")
+    public String getType() {
+        return this.Type;
+    }
+
+    @JsonProperty("Type")
+    public void setType(String Type) {
+        this.Type = Type;
+    }
+
+    private String NamespacePrefix;
+
+    @JsonProperty("NamespacePrefix")
+    public String getNamespacePrefix() {
+        return this.NamespacePrefix;
+    }
+
+    @JsonProperty("NamespacePrefix")
+    public void setNamespacePrefix(String NamespacePrefix) {
+        this.NamespacePrefix = NamespacePrefix;
+    }
+
+    private AbstractDescribedSObjectBase CreatedBy;
+
+    @JsonProperty("CreatedBy")
+    public AbstractDescribedSObjectBase getCreatedBy() {
+        return this.CreatedBy;
+    }
+
+    @JsonProperty("CreatedBy")
+    public void setCreatedBy(AbstractDescribedSObjectBase CreatedBy) {
+        this.CreatedBy = CreatedBy;
+    }
+    private AbstractDescribedSObjectBase LastModifiedBy;
+
+    @JsonProperty("LastModifiedBy")
+    public AbstractDescribedSObjectBase getLastModifiedBy() {
+        return this.LastModifiedBy;
+    }
+
+    @JsonProperty("LastModifiedBy")
+    public void setLastModifiedBy(AbstractDescribedSObjectBase LastModifiedBy) {
+        this.LastModifiedBy = LastModifiedBy;
+    }
+    private QueryRecordsFolder SubFolders;
+
+    @JsonProperty("SubFolders")
+    public QueryRecordsFolder getSubFolders() {
+        return SubFolders;
+    }
+
+    @JsonProperty("SubFolders")
+    public void setSubFolders(QueryRecordsFolder SubFolders) {
+        this.SubFolders = SubFolders;
+    }
+
+    @Override
+    public final SObjectDescription description() {
+        return DESCRIPTION;
+    }
+
+    private static SObjectDescription createSObjectDescription() {
+        final SObjectDescription description = new SObjectDescription();
+
+
+
+        final List<SObjectField> fields1 = new ArrayList<>();
+        description.setFields(fields1);
+
+        final SObjectField sObjectField1 = createField("Id", "Folder ID", "id", "tns:ID", 18, false, false, false, false, false, false, true);
+        fields1.add(sObjectField1);
+        final SObjectField sObjectField2 = createField("ParentId", "Folder ID", "reference", "tns:ID", 18, false, true, false, false, false, false, false);
+        fields1.add(sObjectField2);
+        final SObjectField sObjectField3 = createField("Name", "Name", "string", "xsd:string", 40, false, false, true, false, false, false, true);
+        fields1.add(sObjectField3);
+        final SObjectField sObjectField4 = createField("DeveloperName", "Folder Unique Name", "string", "xsd:string", 80, false, true, false, false, false, false, false);
+        fields1.add(sObjectField4);
+        final SObjectField sObjectField5 = createField("AccessType", "Access Type", "picklist", "xsd:string", 40, false, false, false, false, false, false, false);
+        fields1.add(sObjectField5);
+        final SObjectField sObjectField6 = createField("IsReadonly", "Read Only", "boolean", "xsd:boolean", 0, false, false, false, false, false, false, false);
+        fields1.add(sObjectField6);
+        final SObjectField sObjectField7 = createField("Type", "Type", "picklist", "xsd:string", 40, false, false, false, false, false, false, false);
+        fields1.add(sObjectField7);
+        final SObjectField sObjectField8 = createField("NamespacePrefix", "Namespace Prefix", "string", "xsd:string", 15, false, true, false, false, false, false, false);
+        fields1.add(sObjectField8);
+        final SObjectField sObjectField9 = createField("CreatedDate", "Created Date", "datetime", "xsd:dateTime", 0, false, false, false, false, false, false, false);
+        fields1.add(sObjectField9);
+        final SObjectField sObjectField10 = createField("CreatedById", "Created By ID", "reference", "tns:ID", 18, false, false, false, false, false, false, false);
+        fields1.add(sObjectField10);
+        final SObjectField sObjectField11 = createField("LastModifiedDate", "Last Modified Date", "datetime", "xsd:dateTime", 0, false, false, false, false, false, false, false);
+        fields1.add(sObjectField11);
+        final SObjectField sObjectField12 = createField("LastModifiedById", "Last Modified By ID", "reference", "tns:ID", 18, false, false, false, false, false, false, false);
+        fields1.add(sObjectField12);
+        final SObjectField sObjectField13 = createField("SystemModstamp", "System Modstamp", "datetime", "xsd:dateTime", 0, false, false, false, false, false, false, false);
+        fields1.add(sObjectField13);
+
+        description.setLabel("Folder");
+        description.setLabelPlural("Folders");
+        description.setName("Folder");
+
+        final SObjectDescriptionUrls sObjectDescriptionUrls1 = new SObjectDescriptionUrls();
+        sObjectDescriptionUrls1.setDescribe("/services/data/v59.0/sobjects/Folder/describe");
+        sObjectDescriptionUrls1.setRowTemplate("/services/data/v59.0/sobjects/Folder/{ID}");
+        sObjectDescriptionUrls1.setSobject("/services/data/v59.0/sobjects/Folder");
+        description.setUrls(sObjectDescriptionUrls1);
+
+        return description;
+    }
+}
diff --git a/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/dto/generated/QueryRecordsFolder.java b/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/dto/generated/QueryRecordsFolder.java
new file mode 100644
index 00000000000..50a2706fc60
--- /dev/null
+++ b/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/dto/generated/QueryRecordsFolder.java
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+/*
+ * Salesforce Query DTO generated by camel-salesforce-maven-plugin
+ */
+package org.apache.camel.component.salesforce.dto.generated;
+
+import jakarta.annotation.Generated;
+import org.apache.camel.component.salesforce.api.dto.AbstractQueryRecordsBase;
+
+/**
+ * Salesforce QueryRecords DTO for type Folder
+ */
+@Generated("org.apache.camel.maven.CamelSalesforceMojo")
+public class QueryRecordsFolder extends AbstractQueryRecordsBase<Folder> {
+}