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 2021/01/21 19:02:26 UTC

[camel] branch master updated: CAMEL-13455: Add salesforce Bulk API 2.0 support (#4902)

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 2f7c396  CAMEL-13455: Add salesforce Bulk API 2.0 support (#4902)
2f7c396 is described below

commit 2f7c396c3ade615e4b40c24a808af2a5160e83ee
Author: Jeremy Ross <je...@gmail.com>
AuthorDate: Thu Jan 21 13:01:59 2021 -0600

    CAMEL-13455: Add salesforce Bulk API 2.0 support (#4902)
---
 .../camel/catalog/docs/salesforce-component.adoc   |  45 ++-
 .../salesforce/SalesforceComponentConfigurer.java  |   6 +
 .../salesforce/SalesforceEndpointConfigurer.java   |   6 +
 .../salesforce/SalesforceEndpointUriFactory.java   |   3 +-
 .../camel/component/salesforce/salesforce.json     |   4 +-
 .../src/main/docs/salesforce-component.adoc        |  45 ++-
 .../component/salesforce/SalesforceEndpoint.java   |  21 +-
 .../salesforce/SalesforceEndpointConfig.java       |  15 +
 .../component/salesforce/SalesforceProducer.java   |  27 ++
 .../salesforce/api/dto/bulkv2/AbstractBulkDTO.java |  24 ++
 .../api/dto/bulkv2/ColumnDelimiterEnum.java        |  46 +++
 .../api/dto/bulkv2/ConcurrencyModeEnum.java        |  39 ++
 .../salesforce/api/dto/bulkv2/ContentTypeEnum.java |  42 +++
 .../component/salesforce/api/dto/bulkv2/Job.java   |  84 +++++
 .../salesforce/api/dto/bulkv2/JobBase.java         | 158 ++++++++
 .../salesforce/api/dto/bulkv2/JobStateEnum.java    |  50 +++
 .../salesforce/api/dto/bulkv2/JobTypeEnum.java     |  46 +++
 .../component/salesforce/api/dto/bulkv2/Jobs.java  |  49 +++
 .../salesforce/api/dto/bulkv2/LineEndingEnum.java  |  42 +++
 .../salesforce/api/dto/bulkv2/OperationEnum.java   |  52 +++
 .../salesforce/api/dto/bulkv2/QueryJob.java        |  38 ++
 .../salesforce/api/dto/bulkv2/QueryJobs.java       |  49 +++
 .../salesforce/internal/OperationName.java         |  18 +
 .../internal/client/BulkApiV2Client.java           |  88 +++++
 .../internal/client/DefaultBulkApiV2Client.java    | 400 +++++++++++++++++++++
 .../internal/processor/BulkApiV2Processor.java     | 372 +++++++++++++++++++
 .../BulkApiV2IngestJobIntegrationTest.java         | 149 ++++++++
 .../BulkApiV2QueryJobIntegrationTest.java          | 144 ++++++++
 .../dsl/SalesforceComponentBuilderFactory.java     |  18 +
 .../builder/endpoint/StaticEndpointBuilders.java   |  18 +-
 .../dsl/SalesforceEndpointBuilderFactory.java      |  68 +++-
 .../modules/ROOT/pages/salesforce-component.adoc   |  45 ++-
 32 files changed, 2182 insertions(+), 29 deletions(-)

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 3acfc05..4fce36c 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
@@ -176,7 +176,42 @@ list of errors while creating the new object.
 ...to("salesforce:upsertSObject?sObjectIdName=Name")...
 ----
 
-=== Rest Bulk API
+=== Bulk 2.0 API
+
+The Bulk 2.0 API has a simplified model over the original Bulk API. Use it to quickly load a large
+amount of data into salesforce, or query a large amount of data out of salesforce. Data must be
+provided in CSV format. The minimum API version for Bulk 2.0 is v41.0. The minimum API version for
+Bulk Queries is v47.0. DTO classes mentioned below are from the
+`org.apache.camel.component.salesforce.api.dto.bulkv2` package. The following operations are supported:
+
+* *bulk2CreateJob* - Create a bulk job. Supply an instance of `Job` in the message body.
+* *bulk2GetJob* - Get an existing Job. `jobId` parameter is required.
+* *bulk2CreateBatch* - Add a Batch of CSV records to a job. Supply CSV data in the message body.
+The first row must contain headers. `jobId` parameter is required.
+* *bulk2CloseJob* - Close a job. You must close the job in order for it to be processed or
+aborted/deleted. `jobId` parameter is required.
+* *bulk2AbortJob* - Abort a job. `jobId` parameter is required.
+* *bulk2DeleteJob* - Delete a job. `jobId` parameter is required.
+* *bulk2GetSuccessfulResults* - Get successful results for a job. Returned message body will contain
+an InputStream of CSV data. `jobId` parameter is required.
+* *bulk2GetFailedResults* - Get failed results for a job. Returned message body will contain an
+InputStream of CSV data. `jobId` parameter is required.
+* *bulk2GetUnprocessedRecords* - Get unprocessed records for a job. Returned message body will
+contain an InputStream of CSV data. `jobId` parameter is required.
+* *bulk2GetAllJobs* - Get all jobs. Response body is an instance of `Jobs`. If the `done` property
+is false, there are additional pages to fetch, and the `nextRecordsUrl` property contains the value
+to be set in the `queryLocator` parameter on subsequent calls.
+* *bulk2CreateQueryJob* - Create a bulk query job. Supply an instance of `QueryJob` in the message
+body.
+* *bulk2GetQueryJob* - Get a bulk query job. `jobId` parameter is required.
+* *bulk2GetQueryJobResults* - Get bulk query job results. `jobId` parameter is required.
+* *bulk2AbortQueryJob* - Abort a bulk query job. `jobId` parameter is required.
+* *bulk2DeleteQueryJob* - Delete a bulk query job. `jobId` parameter is required.
+* *bulk2GetAllQueryJobs* - Get all jobs. Response body is an instance of `QueryJobs`. If the `done`
+property is false, there are additional pages to fetch, and the `nextRecordsUrl` property contains
+the value to be set in the `queryLocator` parameter on subsequent calls.
+
+=== Rest Bulk (original) API
 
 Producer endpoints can use the following APIs. All Job data formats,
 i.e. xml, csv, zip/xml, and zip/csv are supported.  +
@@ -683,7 +718,7 @@ for details on how to generate the DTO.
 
 
 // component options: START
-The Salesforce component supports 74 options, which are listed below.
+The Salesforce component supports 75 options, which are listed below.
 
 
 
@@ -718,6 +753,7 @@ The Salesforce component supports 74 options, which are listed below.
 | *notifyForOperationUpdate* (common) | Notify for update operation, defaults to false (API version = 29.0) |  | Boolean
 | *objectMapper* (common) | Custom Jackson ObjectMapper to use when serializing/deserializing Salesforce objects. |  | ObjectMapper
 | *packages* (common) | In what packages are the generated DTO classes. Typically the classes would be generated using camel-salesforce-maven-plugin. This must be set if using the XML format. Also, set it if using the generated DTOs to gain the benefit of using short SObject names in parameters/header values. Multiple packages can be separated by comma. |  | String
+| *queryLocator* (common) | Query Locator provided by salesforce for use when a query results in more records than can be retrieved in a single call. Use this value in a subsequent call to retrieve additional records. |  | String
 | *rawPayload* (common) | Use raw payload String for request and response (either JSON or XML depending on format), instead of DTOs, false by default | false | boolean
 | *reportId* (common) | Salesforce1 Analytics report Id |  | String
 | *reportMetadata* (common) | Salesforce1 Analytics report metadata for filtering |  | ReportMetadata
@@ -791,12 +827,12 @@ with the following path and query parameters:
 [width="100%",cols="2,5,^1,2",options="header"]
 |===
 | Name | Description | Default | Type
-| *operationName* | The operation to use. There are 43 enums and the value can be one of: getVersions, getResources, getGlobalObjects, getBasicInfo, getDescription, getSObject, createSObject, updateSObject, deleteSObject, getSObjectWithId, upsertSObject, deleteSObjectWithId, getBlobField, query, queryMore, queryAll, search, apexCall, recent, createJob, getJob, closeJob, abortJob, createBatch, getBatch, getAllBatches, getRequest, getResults, createBatchQuery, getQueryResultIds, getQueryRe [...]
+| *operationName* | The operation to use. There are 59 enums and the value can be one of: getVersions, getResources, getGlobalObjects, getBasicInfo, getDescription, getSObject, createSObject, updateSObject, deleteSObject, getSObjectWithId, upsertSObject, deleteSObjectWithId, getBlobField, query, queryMore, queryAll, search, apexCall, recent, createJob, getJob, closeJob, abortJob, createBatch, getBatch, getAllBatches, getRequest, getResults, createBatchQuery, getQueryResultIds, getQueryRe [...]
 | *topicName* | The name of the topic/channel to use |  | String
 |===
 
 
-=== Query Parameters (44 parameters):
+=== Query Parameters (45 parameters):
 
 
 [width="100%",cols="2,5,^1,2",options="header"]
@@ -825,6 +861,7 @@ with the following path and query parameters:
 | *notifyForOperationUndelete* (common) | Notify for un-delete operation, defaults to false (API version = 29.0) |  | Boolean
 | *notifyForOperationUpdate* (common) | Notify for update operation, defaults to false (API version = 29.0) |  | Boolean
 | *objectMapper* (common) | Custom Jackson ObjectMapper to use when serializing/deserializing Salesforce objects. |  | ObjectMapper
+| *queryLocator* (common) | Query Locator provided by salesforce for use when a query results in more records than can be retrieved in a single call. Use this value in a subsequent call to retrieve additional records. |  | String
 | *rawPayload* (common) | Use raw payload String for request and response (either JSON or XML depending on format), instead of DTOs, false by default | false | boolean
 | *reportId* (common) | Salesforce1 Analytics report Id |  | String
 | *reportMetadata* (common) | Salesforce1 Analytics report metadata for filtering |  | ReportMetadata
diff --git a/components/camel-salesforce/camel-salesforce-component/src/generated/java/org/apache/camel/component/salesforce/SalesforceComponentConfigurer.java b/components/camel-salesforce/camel-salesforce-component/src/generated/java/org/apache/camel/component/salesforce/SalesforceComponentConfigurer.java
index 440be38..d4201da 100644
--- a/components/camel-salesforce/camel-salesforce-component/src/generated/java/org/apache/camel/component/salesforce/SalesforceComponentConfigurer.java
+++ b/components/camel-salesforce/camel-salesforce-component/src/generated/java/org/apache/camel/component/salesforce/SalesforceComponentConfigurer.java
@@ -134,6 +134,8 @@ public class SalesforceComponentConfigurer extends PropertyConfigurerSupport imp
         case "objectMapper": getOrCreateConfig(target).setObjectMapper(property(camelContext, com.fasterxml.jackson.databind.ObjectMapper.class, value)); return true;
         case "packages": target.setPackages(property(camelContext, java.lang.String.class, value)); return true;
         case "password": target.setPassword(property(camelContext, java.lang.String.class, value)); return true;
+        case "querylocator":
+        case "queryLocator": getOrCreateConfig(target).setQueryLocator(property(camelContext, java.lang.String.class, value)); return true;
         case "rawpayload":
         case "rawPayload": getOrCreateConfig(target).setRawPayload(property(camelContext, boolean.class, value)); return true;
         case "refreshtoken":
@@ -283,6 +285,8 @@ public class SalesforceComponentConfigurer extends PropertyConfigurerSupport imp
         case "objectMapper": return com.fasterxml.jackson.databind.ObjectMapper.class;
         case "packages": return java.lang.String.class;
         case "password": return java.lang.String.class;
+        case "querylocator":
+        case "queryLocator": return java.lang.String.class;
         case "rawpayload":
         case "rawPayload": return boolean.class;
         case "refreshtoken":
@@ -433,6 +437,8 @@ public class SalesforceComponentConfigurer extends PropertyConfigurerSupport imp
         case "objectMapper": return getOrCreateConfig(target).getObjectMapper();
         case "packages": return target.getPackages();
         case "password": return target.getPassword();
+        case "querylocator":
+        case "queryLocator": return getOrCreateConfig(target).getQueryLocator();
         case "rawpayload":
         case "rawPayload": return getOrCreateConfig(target).isRawPayload();
         case "refreshtoken":
diff --git a/components/camel-salesforce/camel-salesforce-component/src/generated/java/org/apache/camel/component/salesforce/SalesforceEndpointConfigurer.java b/components/camel-salesforce/camel-salesforce-component/src/generated/java/org/apache/camel/component/salesforce/SalesforceEndpointConfigurer.java
index 4879edc..91c8420 100644
--- a/components/camel-salesforce/camel-salesforce-component/src/generated/java/org/apache/camel/component/salesforce/SalesforceEndpointConfigurer.java
+++ b/components/camel-salesforce/camel-salesforce-component/src/generated/java/org/apache/camel/component/salesforce/SalesforceEndpointConfigurer.java
@@ -77,6 +77,8 @@ public class SalesforceEndpointConfigurer extends PropertyConfigurerSupport impl
         case "notifyForOperations": target.getConfiguration().setNotifyForOperations(property(camelContext, org.apache.camel.component.salesforce.internal.dto.NotifyForOperationsEnum.class, value)); return true;
         case "objectmapper":
         case "objectMapper": target.getConfiguration().setObjectMapper(property(camelContext, com.fasterxml.jackson.databind.ObjectMapper.class, value)); return true;
+        case "querylocator":
+        case "queryLocator": target.getConfiguration().setQueryLocator(property(camelContext, java.lang.String.class, value)); return true;
         case "rawpayload":
         case "rawPayload": target.getConfiguration().setRawPayload(property(camelContext, boolean.class, value)); return true;
         case "replayid":
@@ -170,6 +172,8 @@ public class SalesforceEndpointConfigurer extends PropertyConfigurerSupport impl
         case "notifyForOperations": return org.apache.camel.component.salesforce.internal.dto.NotifyForOperationsEnum.class;
         case "objectmapper":
         case "objectMapper": return com.fasterxml.jackson.databind.ObjectMapper.class;
+        case "querylocator":
+        case "queryLocator": return java.lang.String.class;
         case "rawpayload":
         case "rawPayload": return boolean.class;
         case "replayid":
@@ -264,6 +268,8 @@ public class SalesforceEndpointConfigurer extends PropertyConfigurerSupport impl
         case "notifyForOperations": return target.getConfiguration().getNotifyForOperations();
         case "objectmapper":
         case "objectMapper": return target.getConfiguration().getObjectMapper();
+        case "querylocator":
+        case "queryLocator": return target.getConfiguration().getQueryLocator();
         case "rawpayload":
         case "rawPayload": return target.getConfiguration().isRawPayload();
         case "replayid":
diff --git a/components/camel-salesforce/camel-salesforce-component/src/generated/java/org/apache/camel/component/salesforce/SalesforceEndpointUriFactory.java b/components/camel-salesforce/camel-salesforce-component/src/generated/java/org/apache/camel/component/salesforce/SalesforceEndpointUriFactory.java
index 34f1d5f..9ca4520 100644
--- a/components/camel-salesforce/camel-salesforce-component/src/generated/java/org/apache/camel/component/salesforce/SalesforceEndpointUriFactory.java
+++ b/components/camel-salesforce/camel-salesforce-component/src/generated/java/org/apache/camel/component/salesforce/SalesforceEndpointUriFactory.java
@@ -20,7 +20,7 @@ public class SalesforceEndpointUriFactory extends org.apache.camel.support.compo
     private static final Set<String> PROPERTY_NAMES;
     private static final Set<String> SECRET_PROPERTY_NAMES;
     static {
-        Set<String> props = new HashSet<>(46);
+        Set<String> props = new HashSet<>(47);
         props.add("initialReplayIdMap");
         props.add("notifyForOperations");
         props.add("sObjectQuery");
@@ -40,6 +40,7 @@ public class SalesforceEndpointUriFactory extends org.apache.camel.support.compo
         props.add("reportMetadata");
         props.add("limit");
         props.add("apexQueryParams");
+        props.add("queryLocator");
         props.add("contentType");
         props.add("includeDetails");
         props.add("sObjectFields");
diff --git a/components/camel-salesforce/camel-salesforce-component/src/generated/resources/org/apache/camel/component/salesforce/salesforce.json b/components/camel-salesforce/camel-salesforce-component/src/generated/resources/org/apache/camel/component/salesforce/salesforce.json
index ad6f6cc..9f84f8e 100644
--- a/components/camel-salesforce/camel-salesforce-component/src/generated/resources/org/apache/camel/component/salesforce/salesforce.json
+++ b/components/camel-salesforce/camel-salesforce-component/src/generated/resources/org/apache/camel/component/salesforce/salesforce.json
@@ -51,6 +51,7 @@
     "notifyForOperationUpdate": { "kind": "property", "displayName": "Notify For Operation Update", "group": "common", "label": "", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.salesforce.SalesforceEndpointConfig", "configurationField": "config", "description": "Notify for update operation, defaults to false (API version = 29.0)" },
     "objectMapper": { "kind": "property", "displayName": "Object Mapper", "group": "common", "label": "", "required": false, "type": "object", "javaType": "com.fasterxml.jackson.databind.ObjectMapper", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.salesforce.SalesforceEndpointConfig", "configurationField": "config", "description": "Custom Jackson ObjectMapper to use when serializing\/deserializing Salesforce objects." },
     "packages": { "kind": "property", "displayName": "Packages", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "In what packages are the generated DTO classes. Typically the classes would be generated using camel-salesforce-maven-plugin. This must be set if using the XML format. Also, set it if using the generated DTOs to gain the benefit of using short SO [...]
+    "queryLocator": { "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 single call. U [...]
     "rawPayload": { "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 of DTOs, false [...]
     "reportId": { "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": { "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 filtering" },
@@ -99,7 +100,7 @@
     "userName": { "kind": "property", "displayName": "User Name", "group": "security", "label": "common,security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Username used in OAuth flow to gain access to access token. It's easy to get started with password OAuth flow, but in general one should avoid it as it is deemed less secure than other flows." }
   },
   "properties": {
-    "operationName": { "kind": "path", "displayName": "Operation Name", "group": "producer", "label": "producer", "required": false, "type": "object", "javaType": "org.apache.camel.component.salesforce.internal.OperationName", "enum": [ "getVersions", "getResources", "getGlobalObjects", "getBasicInfo", "getDescription", "getSObject", "createSObject", "updateSObject", "deleteSObject", "getSObjectWithId", "upsertSObject", "deleteSObjectWithId", "getBlobField", "query", "queryMore", "queryA [...]
+    "operationName": { "kind": "path", "displayName": "Operation Name", "group": "producer", "label": "producer", "required": false, "type": "object", "javaType": "org.apache.camel.component.salesforce.internal.OperationName", "enum": [ "getVersions", "getResources", "getGlobalObjects", "getBasicInfo", "getDescription", "getSObject", "createSObject", "updateSObject", "deleteSObject", "getSObjectWithId", "upsertSObject", "deleteSObjectWithId", "getBlobField", "query", "queryMore", "queryA [...]
     "topicName": { "kind": "path", "displayName": "Topic Name", "group": "consumer", "label": "consumer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The name of the topic\/channel to use" },
     "apexMethod": { "kind": "parameter", "displayName": "Apex Method", "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": "APEX method name" },
     "apexQueryParams": { "kind": "parameter", "displayName": "Apex Query Params", "group": "common", "label": "", "required": false, "type": "object", "javaType": "java.util.Map<java.lang.String, java.lang.Object>", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.salesforce.SalesforceEndpointConfig", "configurationField": "configuration", "description": "Query params for APEX method" },
@@ -124,6 +125,7 @@
     "notifyForOperationUndelete": { "kind": "parameter", "displayName": "Notify For Operation Undelete", "group": "common", "label": "", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.salesforce.SalesforceEndpointConfig", "configurationField": "configuration", "description": "Notify for un-delete operation, defaults to false (API version = 29.0)" },
     "notifyForOperationUpdate": { "kind": "parameter", "displayName": "Notify For Operation Update", "group": "common", "label": "", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.salesforce.SalesforceEndpointConfig", "configurationField": "configuration", "description": "Notify for update operation, defaults to false (API version = 29.0)" },
     "objectMapper": { "kind": "parameter", "displayName": "Object Mapper", "group": "common", "label": "", "required": false, "type": "object", "javaType": "com.fasterxml.jackson.databind.ObjectMapper", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.salesforce.SalesforceEndpointConfig", "configurationField": "configuration", "description": "Custom Jackson ObjectMapper to use when serializing\/deserializing Salesforce objects." },
+    "queryLocator": { "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 retrieved in a single [...]
     "rawPayload": { "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), instead of DTO [...]
     "reportId": { "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": { "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 metadata for filtering" },
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 3acfc05..4fce36c 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
@@ -176,7 +176,42 @@ list of errors while creating the new object.
 ...to("salesforce:upsertSObject?sObjectIdName=Name")...
 ----
 
-=== Rest Bulk API
+=== Bulk 2.0 API
+
+The Bulk 2.0 API has a simplified model over the original Bulk API. Use it to quickly load a large
+amount of data into salesforce, or query a large amount of data out of salesforce. Data must be
+provided in CSV format. The minimum API version for Bulk 2.0 is v41.0. The minimum API version for
+Bulk Queries is v47.0. DTO classes mentioned below are from the
+`org.apache.camel.component.salesforce.api.dto.bulkv2` package. The following operations are supported:
+
+* *bulk2CreateJob* - Create a bulk job. Supply an instance of `Job` in the message body.
+* *bulk2GetJob* - Get an existing Job. `jobId` parameter is required.
+* *bulk2CreateBatch* - Add a Batch of CSV records to a job. Supply CSV data in the message body.
+The first row must contain headers. `jobId` parameter is required.
+* *bulk2CloseJob* - Close a job. You must close the job in order for it to be processed or
+aborted/deleted. `jobId` parameter is required.
+* *bulk2AbortJob* - Abort a job. `jobId` parameter is required.
+* *bulk2DeleteJob* - Delete a job. `jobId` parameter is required.
+* *bulk2GetSuccessfulResults* - Get successful results for a job. Returned message body will contain
+an InputStream of CSV data. `jobId` parameter is required.
+* *bulk2GetFailedResults* - Get failed results for a job. Returned message body will contain an
+InputStream of CSV data. `jobId` parameter is required.
+* *bulk2GetUnprocessedRecords* - Get unprocessed records for a job. Returned message body will
+contain an InputStream of CSV data. `jobId` parameter is required.
+* *bulk2GetAllJobs* - Get all jobs. Response body is an instance of `Jobs`. If the `done` property
+is false, there are additional pages to fetch, and the `nextRecordsUrl` property contains the value
+to be set in the `queryLocator` parameter on subsequent calls.
+* *bulk2CreateQueryJob* - Create a bulk query job. Supply an instance of `QueryJob` in the message
+body.
+* *bulk2GetQueryJob* - Get a bulk query job. `jobId` parameter is required.
+* *bulk2GetQueryJobResults* - Get bulk query job results. `jobId` parameter is required.
+* *bulk2AbortQueryJob* - Abort a bulk query job. `jobId` parameter is required.
+* *bulk2DeleteQueryJob* - Delete a bulk query job. `jobId` parameter is required.
+* *bulk2GetAllQueryJobs* - Get all jobs. Response body is an instance of `QueryJobs`. If the `done`
+property is false, there are additional pages to fetch, and the `nextRecordsUrl` property contains
+the value to be set in the `queryLocator` parameter on subsequent calls.
+
+=== Rest Bulk (original) API
 
 Producer endpoints can use the following APIs. All Job data formats,
 i.e. xml, csv, zip/xml, and zip/csv are supported.  +
@@ -683,7 +718,7 @@ for details on how to generate the DTO.
 
 
 // component options: START
-The Salesforce component supports 74 options, which are listed below.
+The Salesforce component supports 75 options, which are listed below.
 
 
 
@@ -718,6 +753,7 @@ The Salesforce component supports 74 options, which are listed below.
 | *notifyForOperationUpdate* (common) | Notify for update operation, defaults to false (API version = 29.0) |  | Boolean
 | *objectMapper* (common) | Custom Jackson ObjectMapper to use when serializing/deserializing Salesforce objects. |  | ObjectMapper
 | *packages* (common) | In what packages are the generated DTO classes. Typically the classes would be generated using camel-salesforce-maven-plugin. This must be set if using the XML format. Also, set it if using the generated DTOs to gain the benefit of using short SObject names in parameters/header values. Multiple packages can be separated by comma. |  | String
+| *queryLocator* (common) | Query Locator provided by salesforce for use when a query results in more records than can be retrieved in a single call. Use this value in a subsequent call to retrieve additional records. |  | String
 | *rawPayload* (common) | Use raw payload String for request and response (either JSON or XML depending on format), instead of DTOs, false by default | false | boolean
 | *reportId* (common) | Salesforce1 Analytics report Id |  | String
 | *reportMetadata* (common) | Salesforce1 Analytics report metadata for filtering |  | ReportMetadata
@@ -791,12 +827,12 @@ with the following path and query parameters:
 [width="100%",cols="2,5,^1,2",options="header"]
 |===
 | Name | Description | Default | Type
-| *operationName* | The operation to use. There are 43 enums and the value can be one of: getVersions, getResources, getGlobalObjects, getBasicInfo, getDescription, getSObject, createSObject, updateSObject, deleteSObject, getSObjectWithId, upsertSObject, deleteSObjectWithId, getBlobField, query, queryMore, queryAll, search, apexCall, recent, createJob, getJob, closeJob, abortJob, createBatch, getBatch, getAllBatches, getRequest, getResults, createBatchQuery, getQueryResultIds, getQueryRe [...]
+| *operationName* | The operation to use. There are 59 enums and the value can be one of: getVersions, getResources, getGlobalObjects, getBasicInfo, getDescription, getSObject, createSObject, updateSObject, deleteSObject, getSObjectWithId, upsertSObject, deleteSObjectWithId, getBlobField, query, queryMore, queryAll, search, apexCall, recent, createJob, getJob, closeJob, abortJob, createBatch, getBatch, getAllBatches, getRequest, getResults, createBatchQuery, getQueryResultIds, getQueryRe [...]
 | *topicName* | The name of the topic/channel to use |  | String
 |===
 
 
-=== Query Parameters (44 parameters):
+=== Query Parameters (45 parameters):
 
 
 [width="100%",cols="2,5,^1,2",options="header"]
@@ -825,6 +861,7 @@ with the following path and query parameters:
 | *notifyForOperationUndelete* (common) | Notify for un-delete operation, defaults to false (API version = 29.0) |  | Boolean
 | *notifyForOperationUpdate* (common) | Notify for update operation, defaults to false (API version = 29.0) |  | Boolean
 | *objectMapper* (common) | Custom Jackson ObjectMapper to use when serializing/deserializing Salesforce objects. |  | ObjectMapper
+| *queryLocator* (common) | Query Locator provided by salesforce for use when a query results in more records than can be retrieved in a single call. Use this value in a subsequent call to retrieve additional records. |  | String
 | *rawPayload* (common) | Use raw payload String for request and response (either JSON or XML depending on format), instead of DTOs, false by default | false | boolean
 | *reportId* (common) | Salesforce1 Analytics report Id |  | String
 | *reportMetadata* (common) | Salesforce1 Analytics report metadata for filtering |  | ReportMetadata
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 887cfb0..e48b610 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
@@ -39,14 +39,21 @@ public class SalesforceEndpoint extends DefaultEndpoint {
 
     private static final Logger LOG = LoggerFactory.getLogger(SalesforceEndpoint.class);
 
-    @UriPath(label = "producer", description = "The operation to use", enums = "getVersions,getResources,"
-                                                                               + "getGlobalObjects,getBasicInfo,getDescription,getSObject,createSObject,updateSObject,deleteSObject,"
-                                                                               + "getSObjectWithId,upsertSObject,deleteSObjectWithId,getBlobField,query,queryMore,queryAll,search,apexCall,"
-                                                                               + "recent,createJob,getJob,closeJob,abortJob,createBatch,getBatch,getAllBatches,getRequest,getResults,"
-                                                                               + "createBatchQuery,getQueryResultIds,getQueryResult,getRecentReports,getReportDescription,executeSyncReport,"
-                                                                               + "executeAsyncReport,getReportInstances,getReportResults,limits,approval,approvals,composite-tree,"
-                                                                               + "composite-batch,composite")
+    //CHECKSTYLE:OFF
+    @UriPath(label = "producer", description = "The operation to use", enums = "getVersions,"
+            + "getResources,getGlobalObjects,getBasicInfo,getDescription,getSObject,createSObject,"
+            + "updateSObject,deleteSObject,getSObjectWithId,upsertSObject,deleteSObjectWithId,"
+            + "getBlobField,query,queryMore,queryAll,search,apexCall,recent,createJob,getJob,"
+            + "closeJob,abortJob,createBatch,getBatch,getAllBatches,getRequest,getResults,"
+            + "createBatchQuery,getQueryResultIds,getQueryResult,getRecentReports,"
+            + "getReportDescription,executeSyncReport,executeAsyncReport,getReportInstances,"
+            + "getReportResults,limits,approval,approvals,composite-tree,composite-batch,composite,"
+            + "bulk2GetAllJobs,bulk2CreateJob,bulk2GetJob,bulk2CreateBatch,bulk2CloseJob,"
+            + "bulk2AbortJob,bulk2DeleteJob,bulk2GetSuccessfulResults,bulk2GetFailedResults,"
+            + "bulk2GetUnprocessedRecords,bulk2CreateQueryJob,bulk2GetQueryJob,"
+            + "bulk2GetAllQueryJobs,bulk2GetQueryJobResults,bulk2AbortQueryJob,bulk2DeleteQueryJob")
     private final OperationName operationName;
+    //CHECKSTYLE:ON
     @UriPath(label = "consumer", description = "The name of the topic/channel to use")
     private final String topicName;
     @UriParam
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 49603f2..1bcea41 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
@@ -72,6 +72,7 @@ public class SalesforceEndpointConfig implements Cloneable {
     public static final String JOB_ID = "jobId";
     public static final String BATCH_ID = "batchId";
     public static final String RESULT_ID = "resultId";
+    public static final String QUERY_LOCATOR = "queryLocator";
 
     // parameters for Analytics API
     public static final String REPORT_ID = "reportId";
@@ -142,6 +143,8 @@ public class SalesforceEndpointConfig implements Cloneable {
     private String batchId;
     @UriParam
     private String resultId;
+    @UriParam
+    private String queryLocator;
 
     // Streaming API properties
     @UriParam
@@ -452,6 +455,18 @@ public class SalesforceEndpointConfig implements Cloneable {
         return updateTopic;
     }
 
+    public String getQueryLocator() {
+        return queryLocator;
+    }
+
+    /**
+     * Query Locator provided by salesforce for use when a query results in more records than can be retrieved in a
+     * single call. Use this value in a subsequent call to retrieve additional records.
+     */
+    public void setQueryLocator(String queryLocator) {
+        this.queryLocator = queryLocator;
+    }
+
     /**
      * Whether to update an existing Push Topic when using the Streaming API, defaults to false
      */
diff --git a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/SalesforceProducer.java b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/SalesforceProducer.java
index 81283c7..5d5f0e0 100644
--- a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/SalesforceProducer.java
+++ b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/SalesforceProducer.java
@@ -25,6 +25,7 @@ import org.apache.camel.component.salesforce.internal.PayloadFormat;
 import org.apache.camel.component.salesforce.internal.SalesforceSession;
 import org.apache.camel.component.salesforce.internal.processor.AnalyticsApiProcessor;
 import org.apache.camel.component.salesforce.internal.processor.BulkApiProcessor;
+import org.apache.camel.component.salesforce.internal.processor.BulkApiV2Processor;
 import org.apache.camel.component.salesforce.internal.processor.CompositeApiProcessor;
 import org.apache.camel.component.salesforce.internal.processor.JsonRestProcessor;
 import org.apache.camel.component.salesforce.internal.processor.SalesforceProcessor;
@@ -53,6 +54,8 @@ public class SalesforceProducer extends DefaultAsyncProducer {
         final OperationName operationName = endpoint.getOperationName();
         if (isBulkOperation(operationName)) {
             processor = new BulkApiProcessor(endpoint);
+        } else if (isBulkV2Operation(operationName)) {
+            processor = new BulkApiV2Processor(endpoint);
         } else if (isAnalyticsOperation(operationName)) {
             processor = new AnalyticsApiProcessor(endpoint);
         } else if (isCompositeOperation(operationName)) {
@@ -68,6 +71,30 @@ public class SalesforceProducer extends DefaultAsyncProducer {
         }
     }
 
+    private boolean isBulkV2Operation(OperationName operationName) {
+        switch (operationName) {
+            case BULK2_CREATE_JOB:
+            case BULK2_CREATE_BATCH:
+            case BULK2_CLOSE_JOB:
+            case BULK2_GET_JOB:
+            case BULK2_ABORT_JOB:
+            case BULK2_DELETE_JOB:
+            case BULK2_GET_SUCCESSFUL_RESULTS:
+            case BULK2_GET_FAILED_RESULTS:
+            case BULK2_GET_UNPROCESSED_RECORDS:
+            case BULK2_GET_ALL_JOBS:
+            case BULK2_CREATE_QUERY_JOB:
+            case BULK2_GET_QUERY_JOB:
+            case BULK2_GET_QUERY_JOB_RESULTS:
+            case BULK2_ABORT_QUERY_JOB:
+            case BULK2_DELETE_QUERY_JOB:
+            case BULK2_GET_ALL_QUERY_JOBS:
+                return true;
+            default:
+                return false;
+        }
+    }
+
     private static boolean isBulkOperation(OperationName operationName) {
         switch (operationName) {
             case CREATE_JOB:
diff --git a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/bulkv2/AbstractBulkDTO.java b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/bulkv2/AbstractBulkDTO.java
new file mode 100644
index 0000000..7d4bf16
--- /dev/null
+++ b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/bulkv2/AbstractBulkDTO.java
@@ -0,0 +1,24 @@
+/*
+ * 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.api.dto.bulkv2;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import org.apache.camel.component.salesforce.api.dto.AbstractDTOBase;
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public abstract class AbstractBulkDTO extends AbstractDTOBase {
+}
diff --git a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/bulkv2/ColumnDelimiterEnum.java b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/bulkv2/ColumnDelimiterEnum.java
new file mode 100644
index 0000000..295c790
--- /dev/null
+++ b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/bulkv2/ColumnDelimiterEnum.java
@@ -0,0 +1,46 @@
+/*
+ * 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.api.dto.bulkv2;
+
+public enum ColumnDelimiterEnum {
+
+    BACKQUOTE("backquote"),
+    CARET("caret"),
+    COMMA("comma"),
+    PIPE("pipe"),
+    SEMICOLON("semicolon"),
+    TAB("tab");
+
+    private final String value;
+
+    ColumnDelimiterEnum(String value) {
+        this.value = value;
+    }
+
+    public String value() {
+        return value;
+    }
+
+    public static ColumnDelimiterEnum fromValue(String v) {
+        for (ColumnDelimiterEnum c : ColumnDelimiterEnum.values()) {
+            if (c.value.equals(v)) {
+                return c;
+            }
+        }
+        throw new IllegalArgumentException(v);
+    }
+}
diff --git a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/bulkv2/ConcurrencyModeEnum.java b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/bulkv2/ConcurrencyModeEnum.java
new file mode 100644
index 0000000..4b2785f
--- /dev/null
+++ b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/bulkv2/ConcurrencyModeEnum.java
@@ -0,0 +1,39 @@
+/*
+ * 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.api.dto.bulkv2;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public enum ConcurrencyModeEnum {
+    @JsonProperty("Parallel")
+    PARALLEL("Parallel");
+
+    private final String value;
+
+    ConcurrencyModeEnum(String value) {
+        this.value = value;
+    }
+
+    public static ConcurrencyModeEnum fromValue(String v) {
+        for (ConcurrencyModeEnum c : ConcurrencyModeEnum.values()) {
+            if (c.value.equals(v)) {
+                return c;
+            }
+        }
+        throw new IllegalArgumentException(v);
+    }
+}
diff --git a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/bulkv2/ContentTypeEnum.java b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/bulkv2/ContentTypeEnum.java
new file mode 100644
index 0000000..710ecbe
--- /dev/null
+++ b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/bulkv2/ContentTypeEnum.java
@@ -0,0 +1,42 @@
+/*
+ * 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.api.dto.bulkv2;
+
+public enum ContentTypeEnum {
+
+    CSV("CSV"),
+    JSON("JSON"),
+    XML("XML"),
+    ZIP_CSV("ZIP_CSV"),
+    ZIP_JSON("ZIP_JSON"),
+    ZIP_XML("ZIP_XML");
+
+    private final String value;
+
+    ContentTypeEnum(String value) {
+        this.value = value;
+    }
+
+    public static ContentTypeEnum fromValue(String v) {
+        for (ContentTypeEnum c : ContentTypeEnum.values()) {
+            if (c.value.equals(v)) {
+                return c;
+            }
+        }
+        throw new IllegalArgumentException(v);
+    }
+}
diff --git a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/bulkv2/Job.java b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/bulkv2/Job.java
new file mode 100644
index 0000000..1e5247c
--- /dev/null
+++ b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/bulkv2/Job.java
@@ -0,0 +1,84 @@
+/*
+ * 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.api.dto.bulkv2;
+
+public class Job extends JobBase {
+
+    private String assignmentRuleId;
+    private String externalIdFieldName;
+    private OperationEnum operation;
+    private String contentUrl;
+    private Long numberRecordsFailed;
+    private Long apexProcessingTime;
+    private Long apiActiveProcessingTime;
+
+    public String getAssignmentRuleId() {
+        return assignmentRuleId;
+    }
+
+    public void setAssignmentRuleId(String assignmentRuleId) {
+        this.assignmentRuleId = assignmentRuleId;
+    }
+
+    public String getExternalIdFieldName() {
+        return externalIdFieldName;
+    }
+
+    public void setExternalIdFieldName(String externalIdFieldName) {
+        this.externalIdFieldName = externalIdFieldName;
+    }
+
+    public OperationEnum getOperation() {
+        return operation;
+    }
+
+    public void setOperation(OperationEnum operation) {
+        this.operation = operation;
+    }
+
+    public String getContentUrl() {
+        return contentUrl;
+    }
+
+    public void setContentUrl(String contentUrl) {
+        this.contentUrl = contentUrl;
+    }
+
+    public Long getNumberRecordsFailed() {
+        return numberRecordsFailed;
+    }
+
+    public void setNumberRecordsFailed(Long numberRecordsFailed) {
+        this.numberRecordsFailed = numberRecordsFailed;
+    }
+
+    public Long getApexProcessingTime() {
+        return apexProcessingTime;
+    }
+
+    public void setApexProcessingTime(Long apexProcessingTime) {
+        this.apexProcessingTime = apexProcessingTime;
+    }
+
+    public Long getApiActiveProcessingTime() {
+        return apiActiveProcessingTime;
+    }
+
+    public void setApiActiveProcessingTime(Long apiActiveProcessingTime) {
+        this.apiActiveProcessingTime = apiActiveProcessingTime;
+    }
+}
diff --git a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/bulkv2/JobBase.java b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/bulkv2/JobBase.java
new file mode 100644
index 0000000..a35335b
--- /dev/null
+++ b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/bulkv2/JobBase.java
@@ -0,0 +1,158 @@
+/*
+ * 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.api.dto.bulkv2;
+
+import java.time.Instant;
+
+public abstract class JobBase extends AbstractBulkDTO {
+
+    protected String id;
+    protected JobTypeEnum jobType;
+    protected ColumnDelimiterEnum columnDelimiter;
+    protected ContentTypeEnum contentType;
+    protected LineEndingEnum lineEnding;
+    protected String object;
+    protected String apiVersion;
+    protected String createdById;
+    protected Instant createdDate;
+    protected JobStateEnum state;
+    protected ConcurrencyModeEnum concurrencyMode;
+    protected Instant systemModstamp;
+    protected Integer retries;
+    protected Long totalProcessingTime;
+    protected Long numberRecordsProcessed;
+
+    public JobTypeEnum getJobType() {
+        return jobType;
+    }
+
+    public void setJobType(JobTypeEnum jobType) {
+        this.jobType = jobType;
+    }
+
+    public ColumnDelimiterEnum getColumnDelimiter() {
+        return columnDelimiter;
+    }
+
+    public void setColumnDelimiter(ColumnDelimiterEnum columnDelimiter) {
+        this.columnDelimiter = columnDelimiter;
+    }
+
+    public ContentTypeEnum getContentType() {
+        return contentType;
+    }
+
+    public void setContentType(ContentTypeEnum contentType) {
+        this.contentType = contentType;
+    }
+
+    public LineEndingEnum getLineEnding() {
+        return lineEnding;
+    }
+
+    public void setLineEnding(LineEndingEnum lineEnding) {
+        this.lineEnding = lineEnding;
+    }
+
+    public String getObject() {
+        return object;
+    }
+
+    public void setObject(String object) {
+        this.object = object;
+    }
+
+    public String getApiVersion() {
+        return apiVersion;
+    }
+
+    public void setApiVersion(String apiVersion) {
+        this.apiVersion = apiVersion;
+    }
+
+    public String getCreatedById() {
+        return createdById;
+    }
+
+    public void setCreatedById(String createdById) {
+        this.createdById = createdById;
+    }
+
+    public Instant getCreatedDate() {
+        return createdDate;
+    }
+
+    public void setCreatedDate(Instant createdDate) {
+        this.createdDate = createdDate;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public JobStateEnum getState() {
+        return state;
+    }
+
+    public void setState(JobStateEnum state) {
+        this.state = state;
+    }
+
+    public ConcurrencyModeEnum getConcurrencyMode() {
+        return concurrencyMode;
+    }
+
+    public void setConcurrencyMode(ConcurrencyModeEnum concurrencyMode) {
+        this.concurrencyMode = concurrencyMode;
+    }
+
+    public Instant getSystemModstamp() {
+        return systemModstamp;
+    }
+
+    public void setSystemModstamp(Instant systemModstamp) {
+        this.systemModstamp = systemModstamp;
+    }
+
+    public Integer getRetries() {
+        return retries;
+    }
+
+    public void setRetries(Integer retries) {
+        this.retries = retries;
+    }
+
+    public Long getTotalProcessingTime() {
+        return totalProcessingTime;
+    }
+
+    public void setTotalProcessingTime(Long totalProcessingTime) {
+        this.totalProcessingTime = totalProcessingTime;
+    }
+
+    public Long getNumberRecordsProcessed() {
+        return numberRecordsProcessed;
+    }
+
+    public void setNumberRecordsProcessed(Long numberRecordsProcessed) {
+        this.numberRecordsProcessed = numberRecordsProcessed;
+    }
+}
diff --git a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/bulkv2/JobStateEnum.java b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/bulkv2/JobStateEnum.java
new file mode 100644
index 0000000..1a94fcf
--- /dev/null
+++ b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/bulkv2/JobStateEnum.java
@@ -0,0 +1,50 @@
+/*
+ * 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.api.dto.bulkv2;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public enum JobStateEnum {
+
+    @JsonProperty("Open")
+    OPEN("Open"),
+    @JsonProperty("UploadComplete")
+    UPLOAD_COMPLETE("UploadComplete"),
+    @JsonProperty("InProgress")
+    IN_PROGRESS("InProgress"),
+    @JsonProperty("Aborted")
+    ABORTED("Aborted"),
+    @JsonProperty("JobComplete")
+    JOB_COMPLETE("JobComplete"),
+    @JsonProperty("Failed")
+    FAILED("Failed");
+
+    private final String value;
+
+    JobStateEnum(String value) {
+        this.value = value;
+    }
+
+    public static JobStateEnum fromValue(String v) {
+        for (JobStateEnum c : JobStateEnum.values()) {
+            if (c.value.equals(v)) {
+                return c;
+            }
+        }
+        throw new IllegalArgumentException(v);
+    }
+}
diff --git a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/bulkv2/JobTypeEnum.java b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/bulkv2/JobTypeEnum.java
new file mode 100644
index 0000000..88d4b23
--- /dev/null
+++ b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/bulkv2/JobTypeEnum.java
@@ -0,0 +1,46 @@
+/*
+ * 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.api.dto.bulkv2;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public enum JobTypeEnum {
+
+    @JsonProperty("BigObjectIngest")
+    BIGOBJECTINJEST("BigObjectIngest"),
+    @JsonProperty("Classic")
+    CLASSIC("Classic"),
+    @JsonProperty("V2Ingest")
+    V2INGEST("V2Ingest"),
+    @JsonProperty("V2Query")
+    V2Query("V2Query");
+
+    private final String value;
+
+    JobTypeEnum(String value) {
+        this.value = value;
+    }
+
+    public static JobTypeEnum fromValue(String v) {
+        for (JobTypeEnum c : JobTypeEnum.values()) {
+            if (c.value.equals(v)) {
+                return c;
+            }
+        }
+        throw new IllegalArgumentException(v);
+    }
+}
diff --git a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/bulkv2/Jobs.java b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/bulkv2/Jobs.java
new file mode 100644
index 0000000..613a302
--- /dev/null
+++ b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/bulkv2/Jobs.java
@@ -0,0 +1,49 @@
+/*
+ * 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.api.dto.bulkv2;
+
+import java.util.List;
+
+public class Jobs extends AbstractBulkDTO {
+    private Boolean done;
+    private List<Job> records;
+    private String nextRecordsUrl;
+
+    public Boolean getDone() {
+        return done;
+    }
+
+    public void setDone(Boolean done) {
+        this.done = done;
+    }
+
+    public List<Job> getRecords() {
+        return records;
+    }
+
+    public void setRecords(List<Job> records) {
+        this.records = records;
+    }
+
+    public String getNextRecordsUrl() {
+        return nextRecordsUrl;
+    }
+
+    public void setNextRecordsUrl(String nextRecordsUrl) {
+        this.nextRecordsUrl = nextRecordsUrl;
+    }
+}
diff --git a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/bulkv2/LineEndingEnum.java b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/bulkv2/LineEndingEnum.java
new file mode 100644
index 0000000..c7ab0cd
--- /dev/null
+++ b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/bulkv2/LineEndingEnum.java
@@ -0,0 +1,42 @@
+/*
+ * 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.api.dto.bulkv2;
+
+public enum LineEndingEnum {
+
+    LF("lf"),
+    CRLF("crlf");
+
+    private final String value;
+
+    LineEndingEnum(String value) {
+        this.value = value;
+    }
+
+    public String value() {
+        return value;
+    }
+
+    public static LineEndingEnum fromValue(String v) {
+        for (LineEndingEnum c : LineEndingEnum.values()) {
+            if (c.value.equals(v)) {
+                return c;
+            }
+        }
+        throw new IllegalArgumentException(v);
+    }
+}
diff --git a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/bulkv2/OperationEnum.java b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/bulkv2/OperationEnum.java
new file mode 100644
index 0000000..f283d0b
--- /dev/null
+++ b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/bulkv2/OperationEnum.java
@@ -0,0 +1,52 @@
+/*
+ * 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.api.dto.bulkv2;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public enum OperationEnum {
+
+    @JsonProperty("insert")
+    INSERT("insert"),
+    @JsonProperty("delete")
+    DELETE("delete"),
+    @JsonProperty("hardDelete")
+    HARDDELETE("hardDelete"),
+    @JsonProperty("update")
+    UPDATE("update"),
+    @JsonProperty("upsert")
+    UPSERT("upsert"),
+    @JsonProperty("query")
+    QUERY("query"),
+    @JsonProperty("queryAll")
+    QUERY_ALL("queryAll");
+
+    private final String value;
+
+    OperationEnum(String value) {
+        this.value = value;
+    }
+
+    public static OperationEnum fromValue(String v) {
+        for (OperationEnum c : OperationEnum.values()) {
+            if (c.value.equals(v)) {
+                return c;
+            }
+        }
+        throw new IllegalArgumentException(v);
+    }
+}
diff --git a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/bulkv2/QueryJob.java b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/bulkv2/QueryJob.java
new file mode 100644
index 0000000..6357b84
--- /dev/null
+++ b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/bulkv2/QueryJob.java
@@ -0,0 +1,38 @@
+/*
+ * 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.api.dto.bulkv2;
+
+public class QueryJob extends JobBase {
+    private OperationEnum operation;
+    private String query;
+
+    public OperationEnum getOperation() {
+        return operation;
+    }
+
+    public void setOperation(OperationEnum operation) {
+        this.operation = operation;
+    }
+
+    public String getQuery() {
+        return query;
+    }
+
+    public void setQuery(String query) {
+        this.query = query;
+    }
+}
diff --git a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/bulkv2/QueryJobs.java b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/bulkv2/QueryJobs.java
new file mode 100644
index 0000000..53660ce
--- /dev/null
+++ b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/bulkv2/QueryJobs.java
@@ -0,0 +1,49 @@
+/*
+ * 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.api.dto.bulkv2;
+
+import java.util.List;
+
+public class QueryJobs extends AbstractBulkDTO {
+    private Boolean done;
+    private List<QueryJob> records;
+    private String nextRecordsUrl;
+
+    public Boolean getDone() {
+        return done;
+    }
+
+    public void setDone(Boolean done) {
+        this.done = done;
+    }
+
+    public List<QueryJob> getRecords() {
+        return records;
+    }
+
+    public void setRecords(List<QueryJob> records) {
+        this.records = records;
+    }
+
+    public String getNextRecordsUrl() {
+        return nextRecordsUrl;
+    }
+
+    public void setNextRecordsUrl(String nextRecordsUrl) {
+        this.nextRecordsUrl = nextRecordsUrl;
+    }
+}
diff --git a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/OperationName.java b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/OperationName.java
index 382905a..f786de1 100644
--- a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/OperationName.java
+++ b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/OperationName.java
@@ -58,6 +58,24 @@ public enum OperationName {
     GET_QUERY_RESULT_IDS("getQueryResultIds"),
     GET_QUERY_RESULT("getQueryResult"),
 
+    // Bulk API 2.0
+    BULK2_CREATE_JOB("bulk2CreateJob"),
+    BULK2_GET_JOB("bulk2GetJob"),
+    BULK2_CREATE_BATCH("bulk2CreateBatch"),
+    BULK2_CLOSE_JOB("bulk2CloseJob"),
+    BULK2_ABORT_JOB("bulk2AbortJob"),
+    BULK2_DELETE_JOB("bulk2DeleteJob"),
+    BULK2_GET_SUCCESSFUL_RESULTS("bulk2GetSuccessfulResults"),
+    BULK2_GET_FAILED_RESULTS("bulk2GetFailedResults"),
+    BULK2_GET_UNPROCESSED_RECORDS("bulk2GetUnprocessedRecords"),
+    BULK2_GET_ALL_JOBS("bulk2GetAllJobs"),
+    BULK2_CREATE_QUERY_JOB("bulk2CreateQueryJob"),
+    BULK2_GET_QUERY_JOB("bulk2GetQueryJob"),
+    BULK2_GET_ALL_QUERY_JOBS("bulk2GetAllQueryJobs"),
+    BULK2_GET_QUERY_JOB_RESULTS("bulk2GetQueryJobResults"),
+    BULK2_ABORT_QUERY_JOB("bulk2AbortQueryJob"),
+    BULK2_DELETE_QUERY_JOB("bulk2DeleteQueryJob"),
+
     // analytics API
     GET_RECENT_REPORTS("getRecentReports"),
     GET_REPORT_DESCRIPTION("getReportDescription"),
diff --git a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/client/BulkApiV2Client.java b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/client/BulkApiV2Client.java
new file mode 100644
index 0000000..94832d4
--- /dev/null
+++ b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/client/BulkApiV2Client.java
@@ -0,0 +1,88 @@
+/*
+ * 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.internal.client;
+
+import java.io.InputStream;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.camel.component.salesforce.api.SalesforceException;
+import org.apache.camel.component.salesforce.api.dto.bulkv2.Job;
+import org.apache.camel.component.salesforce.api.dto.bulkv2.JobStateEnum;
+import org.apache.camel.component.salesforce.api.dto.bulkv2.Jobs;
+import org.apache.camel.component.salesforce.api.dto.bulkv2.QueryJob;
+import org.apache.camel.component.salesforce.api.dto.bulkv2.QueryJobs;
+
+public interface BulkApiV2Client {
+
+    interface JobResponseCallback {
+        void onResponse(Job job, Map<String, String> headers, SalesforceException ex);
+    }
+
+    interface JobsResponseCallback {
+        void onResponse(Jobs jobs, Map<String, String> headers, SalesforceException ex);
+    }
+
+    interface ResponseCallback {
+        void onResponse(Map<String, String> headers, SalesforceException ex);
+    }
+
+    interface StreamResponseCallback {
+        void onResponse(InputStream inputStream, Map<String, String> headers, SalesforceException ex);
+    }
+
+    interface QueryJobResponseCallback {
+        void onResponse(QueryJob queryJob, Map<String, String> headers, SalesforceException ex);
+    }
+
+    interface QueryJobsResponseCallback {
+        void onResponse(QueryJobs queryJobs, Map<String, String> headers, SalesforceException ex);
+    }
+
+    void createJob(Job job, Map<String, List<String>> header, JobResponseCallback callback);
+
+    void getAllJobs(String queryLocator, Map<String, List<String>> headers, JobsResponseCallback callback);
+
+    void getJob(String jobId, Map<String, List<String>> header, JobResponseCallback callback);
+
+    void createBatch(
+            InputStream batchStream, String jobId, Map<String, List<String>> headers, ResponseCallback callback);
+
+    void changeJobState(
+            String jobId, JobStateEnum state, Map<String, List<String>> headers, JobResponseCallback callback);
+
+    void deleteJob(String jobId, Map<String, List<String>> headers, ResponseCallback callback);
+
+    void getSuccessfulResults(String jobId, Map<String, List<String>> headers, StreamResponseCallback callback);
+
+    void getFailedResults(String jobId, Map<String, List<String>> headers, StreamResponseCallback callback);
+
+    void getUnprocessedRecords(String jobId, Map<String, List<String>> headers, StreamResponseCallback callback);
+
+    void createQueryJob(QueryJob queryJob, Map<String, List<String>> headers, QueryJobResponseCallback callback);
+
+    void getQueryJob(String jobId, Map<String, List<String>> headers, QueryJobResponseCallback callback);
+
+    void getQueryJobResults(String jobId, Map<String, List<String>> headers, StreamResponseCallback callback);
+
+    void changeQueryJobState(
+            String jobId, JobStateEnum state, Map<String, List<String>> headers, QueryJobResponseCallback callback);
+
+    void deleteQueryJob(String jobId, Map<String, List<String>> headers, ResponseCallback callback);
+
+    void getAllQueryJobs(String queryLocator, Map<String, List<String>> headers, QueryJobsResponseCallback callback);
+}
diff --git a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/client/DefaultBulkApiV2Client.java b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/client/DefaultBulkApiV2Client.java
new file mode 100644
index 0000000..2fbad6b
--- /dev/null
+++ b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/client/DefaultBulkApiV2Client.java
@@ -0,0 +1,400 @@
+/*
+ * 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.internal.client;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.camel.component.salesforce.SalesforceEndpoint;
+import org.apache.camel.component.salesforce.SalesforceHttpClient;
+import org.apache.camel.component.salesforce.SalesforceLoginConfig;
+import org.apache.camel.component.salesforce.api.SalesforceException;
+import org.apache.camel.component.salesforce.api.dto.RestError;
+import org.apache.camel.component.salesforce.api.dto.bulkv2.Job;
+import org.apache.camel.component.salesforce.api.dto.bulkv2.JobStateEnum;
+import org.apache.camel.component.salesforce.api.dto.bulkv2.Jobs;
+import org.apache.camel.component.salesforce.api.dto.bulkv2.QueryJob;
+import org.apache.camel.component.salesforce.api.dto.bulkv2.QueryJobs;
+import org.apache.camel.component.salesforce.api.utils.JsonUtils;
+import org.apache.camel.component.salesforce.internal.SalesforceSession;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.util.BytesContentProvider;
+import org.eclipse.jetty.client.util.InputStreamContentProvider;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.util.StringUtil;
+
+public class DefaultBulkApiV2Client extends AbstractClientBase implements BulkApiV2Client {
+
+    private static final String AUTHORIZATION_HEADER = "Authorization";
+    private static final String BEARER_PREFIX = "Bearer ";
+
+    private final ObjectMapper objectMapper;
+
+    public DefaultBulkApiV2Client(String version, SalesforceSession session, SalesforceHttpClient httpClient,
+                                  SalesforceLoginConfig loginConfig, SalesforceEndpoint endpoint) throws SalesforceException {
+        super(version, session, httpClient, loginConfig);
+        if (endpoint.getConfiguration().getObjectMapper() != null) {
+            this.objectMapper = endpoint.getConfiguration().getObjectMapper();
+        } else {
+            this.objectMapper = JsonUtils.createObjectMapper();
+        }
+    }
+
+    @Override
+    public void createJob(Job job, Map<String, List<String>> headers, JobResponseCallback callback) {
+        final Request request = getRequest(HttpMethod.POST, jobUrl(null), headers);
+        try {
+            marshalRequest(job, request);
+        } catch (SalesforceException e) {
+            callback.onResponse(null, Collections.emptyMap(), e);
+            return;
+        }
+        doHttpRequestWithJobResponse(callback, request);
+    }
+
+    @Override
+    public void getJob(String jobId, Map<String, List<String>> headers, JobResponseCallback callback) {
+        final Request request = getRequest(HttpMethod.GET, jobUrl(jobId), headers);
+        doHttpRequestWithJobResponse(callback, request);
+    }
+
+    @Override
+    public void createBatch(
+            InputStream batchStream, String jobId, Map<String, List<String>> headers, ResponseCallback callback) {
+        final Request request = getRequest(HttpMethod.PUT, jobUrl(jobId) + "/batches", headers);
+        request.content(new InputStreamContentProvider(batchStream));
+        request.header(HttpHeader.CONTENT_TYPE, "text/csv");
+        doHttpRequest(request, new ClientResponseCallback() {
+            @Override
+            public void onResponse(
+                    InputStream response, Map<String, String> headers, SalesforceException ex) {
+                callback.onResponse(headers, ex);
+            }
+        });
+    }
+
+    @Override
+    public void changeJobState(
+            String jobId, JobStateEnum state, Map<String, List<String>> headers, JobResponseCallback callback) {
+        final Request request = getRequest(HttpMethod.PATCH, jobUrl(jobId), headers);
+        Job job = new Job();
+        job.setId(jobId);
+        job.setState(state);
+        try {
+            marshalRequest(job, request);
+        } catch (SalesforceException e) {
+            callback.onResponse(null, Collections.emptyMap(), e);
+            return;
+        }
+        doHttpRequest(request, new ClientResponseCallback() {
+            @Override
+            public void onResponse(InputStream response, Map<String, String> headers, SalesforceException ex) {
+                if (ex != null) {
+                    callback.onResponse(null, headers, ex);
+                }
+                Job responseJob = null;
+                try {
+                    responseJob = unmarshalResponse(response, request, Job.class);
+                } catch (SalesforceException e) {
+                    ex = e;
+                }
+                callback.onResponse(responseJob, headers, ex);
+            }
+        });
+    }
+
+    @Override
+    public void deleteJob(String jobId, Map<String, List<String>> headers, ResponseCallback callback) {
+        final Request request = getRequest(HttpMethod.DELETE, jobUrl(jobId), headers);
+        doHttpRequest(request, new ClientResponseCallback() {
+            @Override
+            public void onResponse(InputStream response, Map<String, String> headers, SalesforceException ex) {
+                callback.onResponse(headers, ex);
+            }
+        });
+    }
+
+    @Override
+    public void getSuccessfulResults(
+            String jobId, Map<String, List<String>> headers, StreamResponseCallback callback) {
+        final Request request = getRequest(HttpMethod.GET, jobUrl(jobId) + "/successfulResults", headers);
+        doRequestWithCsvResponse(callback, request);
+    }
+
+    @Override
+    public void getFailedResults(String jobId, Map<String, List<String>> headers, StreamResponseCallback callback) {
+        final Request request = getRequest(HttpMethod.GET, jobUrl(jobId) + "/failedResults", headers);
+        doRequestWithCsvResponse(callback, request);
+    }
+
+    @Override
+    public void getUnprocessedRecords(
+            String jobId, Map<String, List<String>> headers, StreamResponseCallback callback) {
+        final Request request = getRequest(HttpMethod.GET, jobUrl(jobId) + "/unprocessedrecords", headers);
+        doRequestWithCsvResponse(callback, request);
+    }
+
+    @Override
+    public void getAllJobs(String queryLocator, Map<String, List<String>> headers, JobsResponseCallback callback) {
+        String url = jobUrl(null);
+        if (queryLocator != null) {
+            url = url + "?queryLocator=" + queryLocator;
+        }
+        final Request request = getRequest(HttpMethod.GET, url, headers);
+        doHttpRequest(request, new ClientResponseCallback() {
+            @Override
+            public void onResponse(InputStream response, Map<String, String> responseHeaders, SalesforceException ex) {
+                if (ex != null) {
+                    callback.onResponse(null, responseHeaders, ex);
+                }
+                Jobs responseJobs = null;
+                try {
+                    responseJobs = DefaultBulkApiV2Client.this.unmarshalResponse(response, request, Jobs.class);
+                } catch (SalesforceException e) {
+                    ex = e;
+                }
+                callback.onResponse(responseJobs, responseHeaders, ex);
+            }
+        });
+    }
+
+    @Override
+    public void createQueryJob(
+            QueryJob queryJob, Map<String, List<String>> headers, QueryJobResponseCallback callback) {
+        final Request request = getRequest(HttpMethod.POST, queryJobUrl(null), headers);
+        try {
+            marshalRequest(queryJob, request);
+        } catch (SalesforceException e) {
+            callback.onResponse(null, Collections.emptyMap(), e);
+            return;
+        }
+        doHttpRequestWithQueryJobResponse(callback, request);
+    }
+
+    @Override
+    public void getQueryJob(String jobId, Map<String, List<String>> headers, QueryJobResponseCallback callback) {
+        final Request request = getRequest(HttpMethod.GET, queryJobUrl(jobId), headers);
+        doHttpRequestWithQueryJobResponse(callback, request);
+    }
+
+    @Override
+    public void getQueryJobResults(String jobId, Map<String, List<String>> headers, StreamResponseCallback callback) {
+        final Request request = getRequest(HttpMethod.GET, queryJobUrl(jobId) + "/results", headers);
+        doRequestWithCsvResponse(callback, request);
+    }
+
+    @Override
+    public void changeQueryJobState(
+            String jobId, JobStateEnum state, Map<String, List<String>> headers, QueryJobResponseCallback callback) {
+        final Request request = getRequest(HttpMethod.PATCH, queryJobUrl(jobId), headers);
+        QueryJob job = new QueryJob();
+        job.setId(jobId);
+        job.setState(state);
+        try {
+            marshalRequest(job, request);
+        } catch (SalesforceException e) {
+            callback.onResponse(null, Collections.emptyMap(), e);
+            return;
+        }
+        doHttpRequest(request, new ClientResponseCallback() {
+            @Override
+            public void onResponse(InputStream response, Map<String, String> headers, SalesforceException ex) {
+                if (ex != null) {
+                    callback.onResponse(null, headers, ex);
+                }
+                QueryJob responseJob = null;
+                try {
+                    responseJob = unmarshalResponse(response, request, QueryJob.class);
+                } catch (SalesforceException e) {
+                    ex = e;
+                }
+                callback.onResponse(responseJob, headers, ex);
+            }
+        });
+    }
+
+    @Override
+    public void deleteQueryJob(String jobId, Map<String, List<String>> headers, ResponseCallback callback) {
+        final Request request = getRequest(HttpMethod.DELETE, queryJobUrl(jobId), headers);
+        doHttpRequest(request, new ClientResponseCallback() {
+            @Override
+            public void onResponse(InputStream response, Map<String, String> headers, SalesforceException ex) {
+                callback.onResponse(headers, ex);
+            }
+        });
+    }
+
+    @Override
+    public void getAllQueryJobs(
+            String queryLocator, Map<String, List<String>> headers, QueryJobsResponseCallback callback) {
+        String url = queryJobUrl(null);
+        if (queryLocator != null) {
+            url = url + "?queryLocator=" + queryLocator;
+        }
+        final Request request = getRequest(HttpMethod.GET, url, headers);
+        doHttpRequest(request, new ClientResponseCallback() {
+            @Override
+            public void onResponse(InputStream response, Map<String, String> responseHeaders, SalesforceException ex) {
+                if (ex != null) {
+                    callback.onResponse(null, responseHeaders, ex);
+                }
+                QueryJobs responseJobs = null;
+                try {
+                    responseJobs = unmarshalResponse(response, request, QueryJobs.class);
+                } catch (SalesforceException e) {
+                    ex = e;
+                }
+                callback.onResponse(responseJobs, responseHeaders, ex);
+            }
+        });
+    }
+
+    @Override
+    protected void doHttpRequest(Request request, ClientResponseCallback callback) {
+        // set access token for all requests
+        setAccessToken(request);
+        if (!request.getHeaders().contains(HttpHeader.CONTENT_TYPE)) {
+            request.header(HttpHeader.CONTENT_TYPE, "application/json");
+        }
+        request.header(HttpHeader.ACCEPT_CHARSET, StringUtil.__UTF8);
+        request.header(HttpHeader.ACCEPT, "application/json");
+        super.doHttpRequest(request, callback);
+    }
+
+    @Override
+    protected SalesforceException createRestException(Response response, InputStream responseContent) {
+        // this must be of type Error
+        try {
+            final List<RestError> errors = unmarshalResponse(responseContent, response.getRequest(),
+                    new TypeReference<List<RestError>>() {
+                    });
+            return new SalesforceException(errors, response.getStatus());
+        } catch (SalesforceException e) {
+            String msg = "Error un-marshaling Salesforce Error: " + e.getMessage();
+            return new SalesforceException(msg, e);
+        }
+    }
+
+    @Override
+    protected void setAccessToken(Request request) {
+        request.getHeaders().put(AUTHORIZATION_HEADER, BEARER_PREFIX + accessToken);
+    }
+
+    private String jobUrl(String jobId) {
+        return super.instanceUrl + "/services/data/v" + version + "/jobs/ingest" +
+               (jobId != null ? "/" + jobId : "");
+    }
+
+    private String queryJobUrl(String jobId) {
+        return super.instanceUrl + "/services/data/v" + version + "/jobs/query" +
+               (jobId != null ? "/" + jobId : "");
+    }
+
+    private void doRequestWithCsvResponse(StreamResponseCallback callback, Request request) {
+        request.accept("text/csv");
+        doHttpRequest(request, callback::onResponse);
+    }
+
+    private void doHttpRequestWithJobResponse(JobResponseCallback callback, Request request) {
+        doHttpRequest(request, new ClientResponseCallback() {
+            @Override
+            public void onResponse(InputStream response, Map<String, String> responseHeaders, SalesforceException ex) {
+                if (ex != null) {
+                    callback.onResponse(null, responseHeaders, ex);
+                }
+                Job responseJob = null;
+                try {
+                    responseJob = DefaultBulkApiV2Client.this.unmarshalResponse(response, request,
+                            Job.class);
+                } catch (SalesforceException e) {
+                    ex = e;
+                }
+                callback.onResponse(responseJob, responseHeaders, ex);
+            }
+        });
+    }
+
+    private void doHttpRequestWithQueryJobResponse(QueryJobResponseCallback callback, Request request) {
+        doHttpRequest(request, new ClientResponseCallback() {
+            @Override
+            public void onResponse(InputStream response, Map<String, String> responseHeaders, SalesforceException ex) {
+                if (ex != null) {
+                    callback.onResponse(null, responseHeaders, ex);
+                }
+                QueryJob queryJob = null;
+                try {
+                    queryJob = DefaultBulkApiV2Client.this.unmarshalResponse(response, request,
+                            QueryJob.class);
+                } catch (SalesforceException e) {
+                    ex = e;
+                }
+                callback.onResponse(queryJob, responseHeaders, ex);
+            }
+        });
+    }
+
+    private void marshalRequest(Object input, Request request) throws SalesforceException {
+        final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        try {
+            objectMapper.writeValue(outputStream, input);
+        } catch (IOException e) {
+            String message = "Error marshaling request: " + e.getMessage();
+            throw new SalesforceException(message, e);
+        }
+        request.content(new BytesContentProvider(outputStream.toByteArray()));
+    }
+
+    private <T> T unmarshalResponse(InputStream response, Request request, Class<T> resultClass)
+            throws SalesforceException {
+        T result = null;
+        if (response != null) {
+            try {
+                result = objectMapper.readValue(response, resultClass);
+            } catch (IOException e) {
+                throw new SalesforceException(
+                        String.format("Error unmarshalling response for {%s:%s} : %s",
+                                request.getMethod(), request.getURI(), e.getMessage()),
+                        e);
+            }
+        }
+        return result;
+    }
+
+    private <T> T unmarshalResponse(InputStream response, Request request, TypeReference<T> typeRef)
+            throws SalesforceException {
+        T result = null;
+        if (response != null) {
+            try {
+                result = objectMapper.readValue(response, typeRef);
+            } catch (IOException e) {
+                throw new SalesforceException(
+                        String.format("Error unmarshalling response for {%s:%s} : %s",
+                                request.getMethod(), request.getURI(), e.getMessage()),
+                        e);
+            }
+        }
+        return result;
+    }
+}
diff --git a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/processor/BulkApiV2Processor.java b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/processor/BulkApiV2Processor.java
new file mode 100644
index 0000000..4833519
--- /dev/null
+++ b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/processor/BulkApiV2Processor.java
@@ -0,0 +1,372 @@
+/*
+ * 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.internal.processor;
+
+import java.io.InputStream;
+import java.util.Map;
+
+import org.apache.camel.AsyncCallback;
+import org.apache.camel.CamelException;
+import org.apache.camel.Exchange;
+import org.apache.camel.InvalidPayloadException;
+import org.apache.camel.Message;
+import org.apache.camel.component.salesforce.SalesforceEndpoint;
+import org.apache.camel.component.salesforce.SalesforceEndpointConfig;
+import org.apache.camel.component.salesforce.api.SalesforceException;
+import org.apache.camel.component.salesforce.api.dto.bulkv2.Job;
+import org.apache.camel.component.salesforce.api.dto.bulkv2.JobStateEnum;
+import org.apache.camel.component.salesforce.api.dto.bulkv2.Jobs;
+import org.apache.camel.component.salesforce.api.dto.bulkv2.QueryJob;
+import org.apache.camel.component.salesforce.api.dto.bulkv2.QueryJobs;
+import org.apache.camel.component.salesforce.internal.client.BulkApiV2Client;
+import org.apache.camel.component.salesforce.internal.client.DefaultBulkApiV2Client;
+import org.apache.camel.support.service.ServiceHelper;
+
+import static org.apache.camel.component.salesforce.SalesforceEndpointConfig.JOB_ID;
+import static org.apache.camel.component.salesforce.SalesforceEndpointConfig.QUERY_LOCATOR;
+import static org.apache.camel.component.salesforce.internal.client.BulkApiV2Client.JobResponseCallback;
+import static org.apache.camel.component.salesforce.internal.client.BulkApiV2Client.ResponseCallback;
+import static org.apache.camel.component.salesforce.internal.client.BulkApiV2Client.StreamResponseCallback;
+
+public class BulkApiV2Processor extends AbstractSalesforceProcessor {
+
+    private BulkApiV2Client bulkClient;
+
+    public BulkApiV2Processor(SalesforceEndpoint endpoint) {
+        super(endpoint);
+    }
+
+    @Override
+    public boolean process(final Exchange exchange, final AsyncCallback callback) {
+        boolean done = false;
+
+        try {
+            switch (operationName) {
+                case BULK2_CREATE_JOB:
+                    processCreateJob(exchange, callback);
+                    break;
+                case BULK2_GET_JOB:
+                    processGetJob(exchange, callback);
+                    break;
+                case BULK2_CREATE_BATCH:
+                    processCreateBatch(exchange, callback);
+                    break;
+                case BULK2_CLOSE_JOB:
+                    processCloseJob(exchange, callback);
+                    break;
+                case BULK2_ABORT_JOB:
+                    processAbortJob(exchange, callback);
+                    break;
+                case BULK2_DELETE_JOB:
+                    deleteJob(exchange, callback);
+                    break;
+                case BULK2_GET_SUCCESSFUL_RESULTS:
+                    processGetSuccessfulResults(exchange, callback);
+                    break;
+                case BULK2_GET_FAILED_RESULTS:
+                    processGetFailedResults(exchange, callback);
+                    break;
+                case BULK2_GET_UNPROCESSED_RECORDS:
+                    processGetUnprocessedRecords(exchange, callback);
+                    break;
+                case BULK2_GET_ALL_JOBS:
+                    processGetAllJobs(exchange, callback);
+                    break;
+                case BULK2_CREATE_QUERY_JOB:
+                    processCreateQueryJob(exchange, callback);
+                    break;
+                case BULK2_GET_QUERY_JOB:
+                    processGetQueryJob(exchange, callback);
+                    break;
+                case BULK2_GET_QUERY_JOB_RESULTS:
+                    processGetQueryJobResults(exchange, callback);
+                    break;
+                case BULK2_ABORT_QUERY_JOB:
+                    processAbortQueryJob(exchange, callback);
+                    break;
+                case BULK2_DELETE_QUERY_JOB:
+                    processDeleteQueryJob(exchange, callback);
+                    break;
+                case BULK2_GET_ALL_QUERY_JOBS:
+                    processGetAllQueryJobs(exchange, callback);
+                    break;
+                default:
+                    throw new SalesforceException(
+                            "Unknown operation name: " + operationName.value(), null);
+            }
+        } catch (SalesforceException e) {
+            exchange.setException(new SalesforceException(
+                    String.format("Error processing %s: [%s] \"%s\"", operationName.value(),
+                            e.getStatusCode(), e.getMessage()),
+                    e));
+            callback.done(true);
+            done = true;
+        } catch (InvalidPayloadException | RuntimeException e) {
+            exchange.setException(new SalesforceException(
+                    String.format("Unexpected Error processing %s: \"%s\"", operationName.value(),
+                            e.getMessage()),
+                    e));
+            callback.done(true);
+            done = true;
+        }
+
+        // continue routing asynchronously if false
+        return done;
+    }
+
+    @Override
+    protected void doStart() throws Exception {
+        super.doStart();
+        this.bulkClient = new DefaultBulkApiV2Client(
+                (String) endpointConfigMap.get(SalesforceEndpointConfig.API_VERSION), session,
+                httpClient, loginConfig, endpoint);
+        ServiceHelper.startService(bulkClient);
+    }
+
+    @Override
+    public void doStop() {
+        // stop the client
+        ServiceHelper.stopService(bulkClient);
+    }
+
+    private void processCreateJob(Exchange exchange, AsyncCallback callback)
+            throws SalesforceException, InvalidPayloadException {
+        Job job = exchange.getIn().getMandatoryBody(Job.class);
+        bulkClient.createJob(job, determineHeaders(exchange),
+                new JobResponseCallback() {
+                    @Override
+                    public void onResponse(Job job, Map<String, String> headers, SalesforceException ex) {
+                        processResponse(exchange, job, headers, ex, callback);
+                    }
+                });
+    }
+
+    private void processGetJob(Exchange exchange, AsyncCallback callback)
+            throws SalesforceException {
+        Job job = exchange.getIn().getBody(Job.class);
+        String jobId;
+        if (job != null) {
+            jobId = job.getId();
+        } else {
+            jobId = getParameter(JOB_ID, exchange, USE_BODY, NOT_OPTIONAL);
+        }
+        bulkClient.getJob(jobId, determineHeaders(exchange),
+                new JobResponseCallback() {
+                    @Override
+                    public void onResponse(Job job, Map<String, String> headers, SalesforceException ex) {
+                        processResponse(exchange, job, headers, ex, callback);
+                    }
+                });
+    }
+
+    private void processCreateBatch(Exchange exchange, AsyncCallback callback)
+            throws SalesforceException {
+        String jobId = getParameter(JOB_ID, exchange, IGNORE_BODY, NOT_OPTIONAL);
+        InputStream input;
+        try {
+            input = exchange.getIn().getMandatoryBody(InputStream.class);
+        } catch (CamelException e) {
+            String msg = "Error preparing batch request: " + e.getMessage();
+            throw new SalesforceException(msg, e);
+        }
+        bulkClient.createBatch(input, jobId, determineHeaders(exchange),
+                new ResponseCallback() {
+                    @Override
+                    public void onResponse(Map<String, String> headers, SalesforceException ex) {
+                        processResponse(exchange, null, headers, ex, callback);
+                    }
+                });
+    }
+
+    private void deleteJob(Exchange exchange, AsyncCallback callback) throws SalesforceException {
+        String jobId = getParameter(JOB_ID, exchange, IGNORE_BODY, NOT_OPTIONAL);
+        bulkClient.deleteJob(jobId, determineHeaders(exchange),
+                new ResponseCallback() {
+                    @Override
+                    public void onResponse(Map<String, String> headers, SalesforceException ex) {
+                        processResponse(exchange, null, headers, ex, callback);
+                    }
+                });
+    }
+
+    private void processAbortJob(Exchange exchange, AsyncCallback callback)
+            throws SalesforceException {
+        String jobId = getParameter(JOB_ID, exchange, IGNORE_BODY, NOT_OPTIONAL);
+        bulkClient.changeJobState(jobId, JobStateEnum.ABORTED, determineHeaders(exchange),
+                new JobResponseCallback() {
+                    @Override
+                    public void onResponse(Job job, Map<String, String> headers, SalesforceException ex) {
+                        processResponse(exchange, job, headers, ex, callback);
+                    }
+                });
+    }
+
+    private void processCloseJob(Exchange exchange, AsyncCallback callback)
+            throws SalesforceException {
+        String jobId = getParameter(JOB_ID, exchange, IGNORE_BODY, NOT_OPTIONAL);
+        bulkClient.changeJobState(jobId, JobStateEnum.UPLOAD_COMPLETE, determineHeaders(exchange),
+                new JobResponseCallback() {
+                    @Override
+                    public void onResponse(Job job, Map<String, String> headers, SalesforceException ex) {
+                        processResponse(exchange, job, headers, ex, callback);
+                    }
+                });
+    }
+
+    private void processGetAllJobs(Exchange exchange, AsyncCallback callback)
+            throws SalesforceException {
+        String queryLocator = getParameter(QUERY_LOCATOR, exchange, IGNORE_BODY, IS_OPTIONAL);
+        bulkClient.getAllJobs(queryLocator, determineHeaders(exchange),
+                new BulkApiV2Client.JobsResponseCallback() {
+                    @Override
+                    public void onResponse(Jobs jobs, Map<String, String> headers, SalesforceException ex) {
+                        BulkApiV2Processor.this.processResponse(exchange, jobs, headers, ex, callback);
+                    }
+                });
+    }
+
+    private void processGetSuccessfulResults(Exchange exchange, AsyncCallback callback)
+            throws SalesforceException {
+        String jobId = getParameter(JOB_ID, exchange, IGNORE_BODY, NOT_OPTIONAL);
+        bulkClient.getSuccessfulResults(jobId, determineHeaders(exchange),
+                new StreamResponseCallback() {
+                    @Override
+                    public void onResponse(
+                            InputStream inputStream, Map<String, String> headers, SalesforceException ex) {
+                        processResponse(exchange, inputStream, headers, ex, callback);
+                    }
+                });
+    }
+
+    private void processGetFailedResults(Exchange exchange, AsyncCallback callback)
+            throws SalesforceException {
+        String jobId = getParameter(JOB_ID, exchange, IGNORE_BODY, NOT_OPTIONAL);
+        bulkClient.getFailedResults(jobId, determineHeaders(exchange),
+                new StreamResponseCallback() {
+                    @Override
+                    public void onResponse(
+                            InputStream inputStream, Map<String, String> headers, SalesforceException ex) {
+                        processResponse(exchange, inputStream, headers, ex, callback);
+                    }
+                });
+    }
+
+    private void processGetUnprocessedRecords(Exchange exchange, AsyncCallback callback)
+            throws SalesforceException {
+        String jobId = getParameter(JOB_ID, exchange, IGNORE_BODY, NOT_OPTIONAL);
+        bulkClient.getUnprocessedRecords(jobId, determineHeaders(exchange),
+                new StreamResponseCallback() {
+                    @Override
+                    public void onResponse(
+                            InputStream inputStream, Map<String, String> headers, SalesforceException ex) {
+                        processResponse(exchange, inputStream, headers, ex, callback);
+                    }
+                });
+    }
+
+    private void processCreateQueryJob(Exchange exchange, AsyncCallback callback)
+            throws SalesforceException, InvalidPayloadException {
+        QueryJob job = exchange.getIn().getMandatoryBody(QueryJob.class);
+        bulkClient.createQueryJob(job, determineHeaders(exchange),
+                new BulkApiV2Client.QueryJobResponseCallback() {
+                    @Override
+                    public void onResponse(
+                            QueryJob job, Map<String, String> headers, SalesforceException ex) {
+                        processResponse(exchange, job, headers, ex, callback);
+                    }
+                });
+    }
+
+    private void processGetQueryJob(Exchange exchange, AsyncCallback callback)
+            throws SalesforceException {
+        QueryJob job = exchange.getIn().getBody(QueryJob.class);
+        String jobId;
+        if (job != null) {
+            jobId = job.getId();
+        } else {
+            jobId = getParameter(JOB_ID, exchange, USE_BODY, NOT_OPTIONAL);
+        }
+        bulkClient.getQueryJob(jobId, determineHeaders(exchange),
+                new BulkApiV2Client.QueryJobResponseCallback() {
+                    @Override
+                    public void onResponse(QueryJob job, Map<String, String> headers, SalesforceException ex) {
+                        processResponse(exchange, job, headers, ex, callback);
+                    }
+                });
+    }
+
+    private void processGetQueryJobResults(Exchange exchange, AsyncCallback callback)
+            throws SalesforceException {
+        String jobId = getParameter(JOB_ID, exchange, IGNORE_BODY, NOT_OPTIONAL);
+        bulkClient.getQueryJobResults(jobId, determineHeaders(exchange),
+                new StreamResponseCallback() {
+                    @Override
+                    public void onResponse(InputStream inputStream, Map<String, String> headers, SalesforceException ex) {
+                        processResponse(exchange, inputStream, headers, ex, callback);
+                    }
+                });
+    }
+
+    private void processAbortQueryJob(Exchange exchange, AsyncCallback callback)
+            throws SalesforceException {
+        String jobId = getParameter(JOB_ID, exchange, IGNORE_BODY, NOT_OPTIONAL);
+        bulkClient.changeQueryJobState(jobId, JobStateEnum.ABORTED, determineHeaders(exchange),
+                new BulkApiV2Client.QueryJobResponseCallback() {
+                    @Override
+                    public void onResponse(QueryJob job, Map<String, String> headers, SalesforceException ex) {
+                        processResponse(exchange, job, headers, ex, callback);
+                    }
+                });
+    }
+
+    private void processDeleteQueryJob(Exchange exchange, AsyncCallback callback)
+            throws SalesforceException {
+        String jobId = getParameter(JOB_ID, exchange, IGNORE_BODY, NOT_OPTIONAL);
+        bulkClient.deleteQueryJob(jobId, determineHeaders(exchange),
+                new ResponseCallback() {
+                    @Override
+                    public void onResponse(Map<String, String> headers, SalesforceException ex) {
+                        processResponse(exchange, null, headers, ex, callback);
+                    }
+                });
+    }
+
+    private void processGetAllQueryJobs(Exchange exchange, AsyncCallback callback)
+            throws SalesforceException {
+        String queryLocator = getParameter(QUERY_LOCATOR, exchange, IGNORE_BODY, IS_OPTIONAL);
+        bulkClient.getAllQueryJobs(queryLocator, determineHeaders(exchange),
+                new BulkApiV2Client.QueryJobsResponseCallback() {
+                    @Override
+                    public void onResponse(QueryJobs jobs, Map<String, String> headers, SalesforceException ex) {
+                        processResponse(exchange, jobs, headers, ex, callback);
+                    }
+                });
+    }
+
+    private void processResponse(
+            Exchange exchange, Object body, Map<String, String> headers, SalesforceException ex,
+            AsyncCallback callback) {
+        final Message message = exchange.getMessage();
+        if (ex != null) {
+            exchange.setException(ex);
+        } else {
+            message.setBody(body);
+        }
+        message.getHeaders().putAll(headers);
+        callback.done(false);
+    }
+}
diff --git a/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/BulkApiV2IngestJobIntegrationTest.java b/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/BulkApiV2IngestJobIntegrationTest.java
new file mode 100644
index 0000000..aeeefc8
--- /dev/null
+++ b/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/BulkApiV2IngestJobIntegrationTest.java
@@ -0,0 +1,149 @@
+/*
+ * 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.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
+import org.apache.camel.CamelExecutionException;
+import org.apache.camel.Exchange;
+import org.apache.camel.component.salesforce.api.SalesforceException;
+import org.apache.camel.component.salesforce.api.dto.bulkv2.Job;
+import org.apache.camel.component.salesforce.api.dto.bulkv2.JobStateEnum;
+import org.apache.camel.component.salesforce.api.dto.bulkv2.Jobs;
+import org.apache.camel.component.salesforce.api.dto.bulkv2.OperationEnum;
+import org.apache.camel.support.DefaultExchange;
+import org.apache.commons.io.IOUtils;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+@SuppressWarnings("BusyWait")
+public class BulkApiV2IngestJobIntegrationTest extends AbstractSalesforceTestBase {
+
+    @Test
+    public void testLifecycle() throws Exception {
+        Job job = new Job();
+        job.setObject("Contact");
+        job.setOperation(OperationEnum.INSERT);
+
+        job = template().requestBody("salesforce:bulk2CreateJob", job, Job.class);
+        assertNotNull(job.getId(), "JobId");
+
+        job = template().requestBody("salesforce:bulk2GetJob", job, Job.class);
+        assertSame(JobStateEnum.OPEN, job.getState(), "Job state");
+
+        Exchange exchange = new DefaultExchange(context());
+        exchange.getIn().setBody("FirstName,LastName\nTestFirst,TestLast");
+        exchange.getIn().setHeader("jobId", job.getId());
+        template.send("salesforce:bulk2CreateBatch", exchange);
+        assertNull(exchange.getException());
+
+        job = template().requestBody("salesforce:bulk2GetJob", job, Job.class);
+        assertSame(JobStateEnum.OPEN, job.getState(), "Job state");
+
+        job = template().requestBodyAndHeader("salesforce:bulk2CloseJob", "", "jobId", job.getId(),
+                Job.class);
+        assertEquals(JobStateEnum.UPLOAD_COMPLETE, job.getState(), "Job state");
+
+        // wait for job to finish
+        while (job.getState() != JobStateEnum.JOB_COMPLETE) {
+            Thread.sleep(2000);
+            job = template().requestBodyAndHeader("salesforce:bulk2GetJob", "", "jobId",
+                    job.getId(), Job.class);
+        }
+
+        InputStream is = template().requestBodyAndHeader("salesforce:bulk2GetSuccessfulResults",
+                "", "jobId", job.getId(), InputStream.class);
+        assertNotNull(is, "Successful results");
+        List<String> successful = IOUtils.readLines(is, StandardCharsets.UTF_8);
+        assertEquals(2, successful.size());
+        assertTrue(successful.get(1).contains("TestFirst"));
+
+        is = template().requestBodyAndHeader("salesforce:bulk2GetFailedResults",
+                "", "jobId", job.getId(), InputStream.class);
+        assertNotNull(is, "Failed results");
+        List<String> failed = IOUtils.readLines(is, StandardCharsets.UTF_8);
+        assertEquals(1, failed.size());
+
+        is = template().requestBodyAndHeader("salesforce:bulk2GetUnprocessedRecords",
+                "", "jobId", job.getId(), InputStream.class);
+        assertNotNull(is, "Unprocessed records");
+        List<String> unprocessed = IOUtils.readLines(is, StandardCharsets.UTF_8);
+        assertEquals(1, unprocessed.size());
+        assertEquals("FirstName,LastName", unprocessed.get(0));
+    }
+
+    @Test
+    public void testAbort() {
+        Job job = new Job();
+        job.setObject("Contact");
+        job.setOperation(OperationEnum.INSERT);
+        job = createJob(job);
+
+        job = template().requestBody("salesforce:bulk2GetJob", job, Job.class);
+        assertSame(JobStateEnum.OPEN, job.getState(), "Job should be OPEN");
+
+        template().sendBodyAndHeader("salesforce:bulk2AbortJob", "", "jobId", job.getId());
+
+        job = template().requestBody("salesforce:bulk2GetJob", job, Job.class);
+        assertSame(JobStateEnum.ABORTED, job.getState(), "Job state");
+    }
+
+    @Test
+    public void testDelete() {
+        Job job = new Job();
+        job.setObject("Contact");
+        job.setOperation(OperationEnum.INSERT);
+        job = createJob(job);
+
+        job = template().requestBody("salesforce:bulk2GetJob", job, Job.class);
+        assertSame(JobStateEnum.OPEN, job.getState(), "Job should be OPEN");
+
+        template().sendBodyAndHeader("salesforce:bulk2AbortJob", "", "jobId", job.getId());
+
+        job = template().requestBody("salesforce:bulk2GetJob", job, Job.class);
+        assertSame(JobStateEnum.ABORTED, job.getState(), "Job state");
+
+        template().sendBodyAndHeader("salesforce:bulk2DeleteJob", "", "jobId", job.getId());
+
+        final Job finalJob = job;
+        CamelExecutionException ex = Assertions.assertThrows(CamelExecutionException.class,
+                () -> template().requestBody("salesforce:bulk2GetJob", finalJob, Job.class));
+        assertEquals(SalesforceException.class, ex.getCause().getClass());
+        SalesforceException sfEx = (SalesforceException) ex.getCause();
+        assertEquals(404, sfEx.getStatusCode());
+    }
+
+    @Test
+    public void testGetAll() {
+        Jobs jobs = template().requestBody("salesforce:bulk2GetAllJobs", "", Jobs.class);
+        assertNotNull(jobs);
+    }
+
+    private Job createJob(Job job) {
+        job = template().requestBody("salesforce:bulk2CreateJob", job, Job.class);
+        assertNotNull(job.getId(), "Missing JobId");
+        return job;
+    }
+}
diff --git a/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/BulkApiV2QueryJobIntegrationTest.java b/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/BulkApiV2QueryJobIntegrationTest.java
new file mode 100644
index 0000000..e554651
--- /dev/null
+++ b/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/BulkApiV2QueryJobIntegrationTest.java
@@ -0,0 +1,144 @@
+/*
+ * 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.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
+import org.apache.camel.CamelExecutionException;
+import org.apache.camel.component.salesforce.api.SalesforceException;
+import org.apache.camel.component.salesforce.api.dto.bulkv2.JobStateEnum;
+import org.apache.camel.component.salesforce.api.dto.bulkv2.OperationEnum;
+import org.apache.camel.component.salesforce.api.dto.bulkv2.QueryJob;
+import org.apache.camel.component.salesforce.api.dto.bulkv2.QueryJobs;
+import org.apache.commons.io.IOUtils;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+@SuppressWarnings("BusyWait")
+public class BulkApiV2QueryJobIntegrationTest extends AbstractSalesforceTestBase {
+
+    @Test
+    public void testQueryLifecycle() throws Exception {
+        QueryJob job = new QueryJob();
+        job.setOperation(OperationEnum.QUERY);
+        job.setQuery("SELECT Id, LastName FROM Contact");
+
+        job = template().requestBody("salesforce:bulk2CreateQueryJob", job, QueryJob.class);
+        assertNotNull(job.getId(), "JobId");
+
+        job = template().requestBodyAndHeader("salesforce:bulk2GetQueryJob", "", "jobId",
+                job.getId(), QueryJob.class);
+
+        // wait for job to finish
+        while (job.getState() != JobStateEnum.JOB_COMPLETE) {
+            Thread.sleep(2000);
+            job = template().requestBodyAndHeader("salesforce:bulk2GetQueryJob", "", "jobId",
+                    job.getId(), QueryJob.class);
+        }
+
+        InputStream is = template().requestBodyAndHeader("salesforce:bulk2GetQueryJobResults",
+                "", "jobId", job.getId(), InputStream.class);
+        assertNotNull(is, "Query Job results");
+        List<String> results = IOUtils.readLines(is, StandardCharsets.UTF_8);
+        assertTrue(results.size() > 0, "Query Job results");
+    }
+
+    @Test
+    public void testQueryAllLifecycle() throws Exception {
+        QueryJob job = new QueryJob();
+        job.setOperation(OperationEnum.QUERY_ALL);
+        job.setQuery("SELECT Id, LastName FROM Contact");
+
+        job = template().requestBody("salesforce:bulk2CreateQueryJob", job, QueryJob.class);
+        assertNotNull(job.getId(), "JobId");
+
+        job = template().requestBodyAndHeader("salesforce:bulk2GetQueryJob", "", "jobId",
+                job.getId(), QueryJob.class);
+
+        // wait for job to finish
+        while (job.getState() != JobStateEnum.JOB_COMPLETE) {
+            Thread.sleep(2000);
+            job = template().requestBodyAndHeader("salesforce:bulk2GetQueryJob", "", "jobId",
+                    job.getId(), QueryJob.class);
+        }
+
+        InputStream is = template().requestBodyAndHeader("salesforce:bulk2GetQueryJobResults",
+                "", "jobId", job.getId(), InputStream.class);
+        assertNotNull(is, "Query Job results");
+        List<String> results = IOUtils.readLines(is, StandardCharsets.UTF_8);
+        assertTrue(results.size() > 0, "Query Job results");
+    }
+
+    @Test
+    public void testAbort() {
+        QueryJob job = new QueryJob();
+        job.setOperation(OperationEnum.QUERY);
+        job.setQuery("SELECT Id, LastName FROM Contact");
+
+        job = template().requestBody("salesforce:bulk2CreateQueryJob", job, QueryJob.class);
+        assertNotNull(job.getId(), "JobId");
+
+        template().sendBodyAndHeader("salesforce:bulk2AbortQueryJob", "", "jobId", job.getId());
+
+        job = template().requestBody("salesforce:bulk2GetQueryJob", job, QueryJob.class);
+        assertTrue(job.getState() == JobStateEnum.ABORTED || job.getState() == JobStateEnum.FAILED,
+                "Expected job to be aborted or failed.");
+    }
+
+    @Test
+    public void testDelete() throws InterruptedException {
+        QueryJob job = new QueryJob();
+        job.setOperation(OperationEnum.QUERY);
+        job.setQuery("SELECT Id, LastName FROM Contact");
+
+        job = template().requestBody("salesforce:bulk2CreateQueryJob", job, QueryJob.class);
+        assertNotNull(job.getId(), "JobId");
+
+        job = template().requestBody("salesforce:bulk2GetQueryJob", job, QueryJob.class);
+        int i = 0;
+        while (job.getState() != JobStateEnum.JOB_COMPLETE) {
+            i++;
+            if (i == 5) {
+                throw new IllegalStateException("Job failed to reach JOB_COMPLETE status.");
+            }
+            Thread.sleep(2000);
+            job = template().requestBody("salesforce:bulk2GetQueryJob", job, QueryJob.class);
+        }
+
+        template().sendBodyAndHeader("salesforce:bulk2DeleteQueryJob", "", "jobId", job.getId());
+
+        final QueryJob finalJob = job;
+        CamelExecutionException ex = Assertions.assertThrows(CamelExecutionException.class,
+                () -> template().requestBody("salesforce:bulk2GetQueryJob", finalJob, QueryJob.class));
+        assertEquals(SalesforceException.class, ex.getCause().getClass());
+        SalesforceException sfEx = (SalesforceException) ex.getCause();
+        assertEquals(404, sfEx.getStatusCode());
+    }
+
+    @Test
+    public void testGetAll() {
+        QueryJobs jobs = template().requestBody("salesforce:bulk2GetAllQueryJobs", "",
+                QueryJobs.class);
+        assertNotNull(jobs);
+    }
+}
diff --git a/core/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/SalesforceComponentBuilderFactory.java b/core/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/SalesforceComponentBuilderFactory.java
index e6bc5a1..6b40214 100644
--- a/core/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/SalesforceComponentBuilderFactory.java
+++ b/core/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/SalesforceComponentBuilderFactory.java
@@ -501,6 +501,23 @@ public interface SalesforceComponentBuilderFactory {
             return this;
         }
         /**
+         * Query Locator provided by salesforce for use when a query results in
+         * more records than can be retrieved in a single call. Use this value
+         * in a subsequent call to retrieve additional records.
+         * 
+         * The option is a: &lt;code&gt;java.lang.String&lt;/code&gt; type.
+         * 
+         * Group: common
+         * 
+         * @param queryLocator the value to set
+         * @return the dsl builder
+         */
+        default SalesforceComponentBuilder queryLocator(
+                java.lang.String queryLocator) {
+            doSetProperty("queryLocator", queryLocator);
+            return this;
+        }
+        /**
          * Use raw payload String for request and response (either JSON or XML
          * depending on format), instead of DTOs, false by default.
          * 
@@ -1314,6 +1331,7 @@ public interface SalesforceComponentBuilderFactory {
             case "notifyForOperationUpdate": getOrCreateConfiguration((SalesforceComponent) component).setNotifyForOperationUpdate((java.lang.Boolean) value); return true;
             case "objectMapper": getOrCreateConfiguration((SalesforceComponent) component).setObjectMapper((com.fasterxml.jackson.databind.ObjectMapper) value); return true;
             case "packages": ((SalesforceComponent) component).setPackages((java.lang.String) value); return true;
+            case "queryLocator": getOrCreateConfiguration((SalesforceComponent) component).setQueryLocator((java.lang.String) value); return true;
             case "rawPayload": getOrCreateConfiguration((SalesforceComponent) component).setRawPayload((boolean) value); return true;
             case "reportId": getOrCreateConfiguration((SalesforceComponent) component).setReportId((java.lang.String) value); return true;
             case "reportMetadata": getOrCreateConfiguration((SalesforceComponent) component).setReportMetadata((org.apache.camel.component.salesforce.api.dto.analytics.reports.ReportMetadata) value); return true;
diff --git a/core/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/StaticEndpointBuilders.java b/core/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/StaticEndpointBuilders.java
index 5991619..2de1b75 100644
--- a/core/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/StaticEndpointBuilders.java
+++ b/core/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/StaticEndpointBuilders.java
@@ -13170,7 +13170,7 @@ public class StaticEndpointBuilders {
      * 
      * Path parameter: operationName
      * The operation to use
-     * There are 43 enums and the value can be one of: getVersions,
+     * There are 59 enums and the value can be one of: getVersions,
      * getResources, getGlobalObjects, getBasicInfo, getDescription, getSObject,
      * createSObject, updateSObject, deleteSObject, getSObjectWithId,
      * upsertSObject, deleteSObjectWithId, getBlobField, query, queryMore,
@@ -13179,7 +13179,12 @@ public class StaticEndpointBuilders {
      * createBatchQuery, getQueryResultIds, getQueryResult, getRecentReports,
      * getReportDescription, executeSyncReport, executeAsyncReport,
      * getReportInstances, getReportResults, limits, approval, approvals,
-     * composite-tree, composite-batch, composite
+     * composite-tree, composite-batch, composite, bulk2GetAllJobs,
+     * bulk2CreateJob, bulk2GetJob, bulk2CreateBatch, bulk2CloseJob,
+     * bulk2AbortJob, bulk2DeleteJob, bulk2GetSuccessfulResults,
+     * bulk2GetFailedResults, bulk2GetUnprocessedRecords, bulk2CreateQueryJob,
+     * bulk2GetQueryJob, bulk2GetAllQueryJobs, bulk2GetQueryJobResults,
+     * bulk2AbortQueryJob, bulk2DeleteQueryJob
      * 
      * Path parameter: topicName
      * The name of the topic/channel to use
@@ -13203,7 +13208,7 @@ public class StaticEndpointBuilders {
      * 
      * Path parameter: operationName
      * The operation to use
-     * There are 43 enums and the value can be one of: getVersions,
+     * There are 59 enums and the value can be one of: getVersions,
      * getResources, getGlobalObjects, getBasicInfo, getDescription, getSObject,
      * createSObject, updateSObject, deleteSObject, getSObjectWithId,
      * upsertSObject, deleteSObjectWithId, getBlobField, query, queryMore,
@@ -13212,7 +13217,12 @@ public class StaticEndpointBuilders {
      * createBatchQuery, getQueryResultIds, getQueryResult, getRecentReports,
      * getReportDescription, executeSyncReport, executeAsyncReport,
      * getReportInstances, getReportResults, limits, approval, approvals,
-     * composite-tree, composite-batch, composite
+     * composite-tree, composite-batch, composite, bulk2GetAllJobs,
+     * bulk2CreateJob, bulk2GetJob, bulk2CreateBatch, bulk2CloseJob,
+     * bulk2AbortJob, bulk2DeleteJob, bulk2GetSuccessfulResults,
+     * bulk2GetFailedResults, bulk2GetUnprocessedRecords, bulk2CreateQueryJob,
+     * bulk2GetQueryJob, bulk2GetAllQueryJobs, bulk2GetQueryJobResults,
+     * bulk2AbortQueryJob, bulk2DeleteQueryJob
      * 
      * Path parameter: topicName
      * The name of the topic/channel to use
diff --git a/core/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/SalesforceEndpointBuilderFactory.java b/core/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/SalesforceEndpointBuilderFactory.java
index 66e4d7a..bea7920 100644
--- a/core/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/SalesforceEndpointBuilderFactory.java
+++ b/core/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/SalesforceEndpointBuilderFactory.java
@@ -702,6 +702,23 @@ public interface SalesforceEndpointBuilderFactory {
             return this;
         }
         /**
+         * Query Locator provided by salesforce for use when a query results in
+         * more records than can be retrieved in a single call. Use this value
+         * in a subsequent call to retrieve additional records.
+         * 
+         * The option is a: &lt;code&gt;java.lang.String&lt;/code&gt; type.
+         * 
+         * Group: common
+         * 
+         * @param queryLocator the value to set
+         * @return the dsl builder
+         */
+        default SalesforceEndpointConsumerBuilder queryLocator(
+                String queryLocator) {
+            doSetProperty("queryLocator", queryLocator);
+            return this;
+        }
+        /**
          * Use raw payload String for request and response (either JSON or XML
          * depending on format), instead of DTOs, false by default.
          * 
@@ -1788,6 +1805,23 @@ public interface SalesforceEndpointBuilderFactory {
             return this;
         }
         /**
+         * Query Locator provided by salesforce for use when a query results in
+         * more records than can be retrieved in a single call. Use this value
+         * in a subsequent call to retrieve additional records.
+         * 
+         * The option is a: &lt;code&gt;java.lang.String&lt;/code&gt; type.
+         * 
+         * Group: common
+         * 
+         * @param queryLocator the value to set
+         * @return the dsl builder
+         */
+        default SalesforceEndpointProducerBuilder queryLocator(
+                String queryLocator) {
+            doSetProperty("queryLocator", queryLocator);
+            return this;
+        }
+        /**
          * Use raw payload String for request and response (either JSON or XML
          * depending on format), instead of DTOs, false by default.
          * 
@@ -2799,6 +2833,22 @@ public interface SalesforceEndpointBuilderFactory {
             return this;
         }
         /**
+         * Query Locator provided by salesforce for use when a query results in
+         * more records than can be retrieved in a single call. Use this value
+         * in a subsequent call to retrieve additional records.
+         * 
+         * The option is a: &lt;code&gt;java.lang.String&lt;/code&gt; type.
+         * 
+         * Group: common
+         * 
+         * @param queryLocator the value to set
+         * @return the dsl builder
+         */
+        default SalesforceEndpointBuilder queryLocator(String queryLocator) {
+            doSetProperty("queryLocator", queryLocator);
+            return this;
+        }
+        /**
          * Use raw payload String for request and response (either JSON or XML
          * depending on format), instead of DTOs, false by default.
          * 
@@ -3132,7 +3182,7 @@ public interface SalesforceEndpointBuilderFactory {
          * 
          * Path parameter: operationName
          * The operation to use
-         * There are 43 enums and the value can be one of: getVersions,
+         * There are 59 enums and the value can be one of: getVersions,
          * getResources, getGlobalObjects, getBasicInfo, getDescription,
          * getSObject, createSObject, updateSObject, deleteSObject,
          * getSObjectWithId, upsertSObject, deleteSObjectWithId, getBlobField,
@@ -3142,7 +3192,12 @@ public interface SalesforceEndpointBuilderFactory {
          * getQueryResult, getRecentReports, getReportDescription,
          * executeSyncReport, executeAsyncReport, getReportInstances,
          * getReportResults, limits, approval, approvals, composite-tree,
-         * composite-batch, composite
+         * composite-batch, composite, bulk2GetAllJobs, bulk2CreateJob,
+         * bulk2GetJob, bulk2CreateBatch, bulk2CloseJob, bulk2AbortJob,
+         * bulk2DeleteJob, bulk2GetSuccessfulResults, bulk2GetFailedResults,
+         * bulk2GetUnprocessedRecords, bulk2CreateQueryJob, bulk2GetQueryJob,
+         * bulk2GetAllQueryJobs, bulk2GetQueryJobResults, bulk2AbortQueryJob,
+         * bulk2DeleteQueryJob
          * 
          * Path parameter: topicName
          * The name of the topic/channel to use
@@ -3165,7 +3220,7 @@ public interface SalesforceEndpointBuilderFactory {
          * 
          * Path parameter: operationName
          * The operation to use
-         * There are 43 enums and the value can be one of: getVersions,
+         * There are 59 enums and the value can be one of: getVersions,
          * getResources, getGlobalObjects, getBasicInfo, getDescription,
          * getSObject, createSObject, updateSObject, deleteSObject,
          * getSObjectWithId, upsertSObject, deleteSObjectWithId, getBlobField,
@@ -3175,7 +3230,12 @@ public interface SalesforceEndpointBuilderFactory {
          * getQueryResult, getRecentReports, getReportDescription,
          * executeSyncReport, executeAsyncReport, getReportInstances,
          * getReportResults, limits, approval, approvals, composite-tree,
-         * composite-batch, composite
+         * composite-batch, composite, bulk2GetAllJobs, bulk2CreateJob,
+         * bulk2GetJob, bulk2CreateBatch, bulk2CloseJob, bulk2AbortJob,
+         * bulk2DeleteJob, bulk2GetSuccessfulResults, bulk2GetFailedResults,
+         * bulk2GetUnprocessedRecords, bulk2CreateQueryJob, bulk2GetQueryJob,
+         * bulk2GetAllQueryJobs, bulk2GetQueryJobResults, bulk2AbortQueryJob,
+         * bulk2DeleteQueryJob
          * 
          * Path parameter: topicName
          * The name of the topic/channel to use
diff --git a/docs/components/modules/ROOT/pages/salesforce-component.adoc b/docs/components/modules/ROOT/pages/salesforce-component.adoc
index 9868c9c..bcf394b 100644
--- a/docs/components/modules/ROOT/pages/salesforce-component.adoc
+++ b/docs/components/modules/ROOT/pages/salesforce-component.adoc
@@ -178,7 +178,42 @@ list of errors while creating the new object.
 ...to("salesforce:upsertSObject?sObjectIdName=Name")...
 ----
 
-=== Rest Bulk API
+=== Bulk 2.0 API
+
+The Bulk 2.0 API has a simplified model over the original Bulk API. Use it to quickly load a large
+amount of data into salesforce, or query a large amount of data out of salesforce. Data must be
+provided in CSV format. The minimum API version for Bulk 2.0 is v41.0. The minimum API version for
+Bulk Queries is v47.0. DTO classes mentioned below are from the
+`org.apache.camel.component.salesforce.api.dto.bulkv2` package. The following operations are supported:
+
+* *bulk2CreateJob* - Create a bulk job. Supply an instance of `Job` in the message body.
+* *bulk2GetJob* - Get an existing Job. `jobId` parameter is required.
+* *bulk2CreateBatch* - Add a Batch of CSV records to a job. Supply CSV data in the message body.
+The first row must contain headers. `jobId` parameter is required.
+* *bulk2CloseJob* - Close a job. You must close the job in order for it to be processed or
+aborted/deleted. `jobId` parameter is required.
+* *bulk2AbortJob* - Abort a job. `jobId` parameter is required.
+* *bulk2DeleteJob* - Delete a job. `jobId` parameter is required.
+* *bulk2GetSuccessfulResults* - Get successful results for a job. Returned message body will contain
+an InputStream of CSV data. `jobId` parameter is required.
+* *bulk2GetFailedResults* - Get failed results for a job. Returned message body will contain an
+InputStream of CSV data. `jobId` parameter is required.
+* *bulk2GetUnprocessedRecords* - Get unprocessed records for a job. Returned message body will
+contain an InputStream of CSV data. `jobId` parameter is required.
+* *bulk2GetAllJobs* - Get all jobs. Response body is an instance of `Jobs`. If the `done` property
+is false, there are additional pages to fetch, and the `nextRecordsUrl` property contains the value
+to be set in the `queryLocator` parameter on subsequent calls.
+* *bulk2CreateQueryJob* - Create a bulk query job. Supply an instance of `QueryJob` in the message
+body.
+* *bulk2GetQueryJob* - Get a bulk query job. `jobId` parameter is required.
+* *bulk2GetQueryJobResults* - Get bulk query job results. `jobId` parameter is required.
+* *bulk2AbortQueryJob* - Abort a bulk query job. `jobId` parameter is required.
+* *bulk2DeleteQueryJob* - Delete a bulk query job. `jobId` parameter is required.
+* *bulk2GetAllQueryJobs* - Get all jobs. Response body is an instance of `QueryJobs`. If the `done`
+property is false, there are additional pages to fetch, and the `nextRecordsUrl` property contains
+the value to be set in the `queryLocator` parameter on subsequent calls.
+
+=== Rest Bulk (original) API
 
 Producer endpoints can use the following APIs. All Job data formats,
 i.e. xml, csv, zip/xml, and zip/csv are supported.  +
@@ -685,7 +720,7 @@ for details on how to generate the DTO.
 
 
 // component options: START
-The Salesforce component supports 74 options, which are listed below.
+The Salesforce component supports 75 options, which are listed below.
 
 
 
@@ -720,6 +755,7 @@ The Salesforce component supports 74 options, which are listed below.
 | *notifyForOperationUpdate* (common) | Notify for update operation, defaults to false (API version = 29.0) |  | Boolean
 | *objectMapper* (common) | Custom Jackson ObjectMapper to use when serializing/deserializing Salesforce objects. |  | ObjectMapper
 | *packages* (common) | In what packages are the generated DTO classes. Typically the classes would be generated using camel-salesforce-maven-plugin. This must be set if using the XML format. Also, set it if using the generated DTOs to gain the benefit of using short SObject names in parameters/header values. Multiple packages can be separated by comma. |  | String
+| *queryLocator* (common) | Query Locator provided by salesforce for use when a query results in more records than can be retrieved in a single call. Use this value in a subsequent call to retrieve additional records. |  | String
 | *rawPayload* (common) | Use raw payload String for request and response (either JSON or XML depending on format), instead of DTOs, false by default | false | boolean
 | *reportId* (common) | Salesforce1 Analytics report Id |  | String
 | *reportMetadata* (common) | Salesforce1 Analytics report metadata for filtering |  | ReportMetadata
@@ -793,12 +829,12 @@ with the following path and query parameters:
 [width="100%",cols="2,5,^1,2",options="header"]
 |===
 | Name | Description | Default | Type
-| *operationName* | The operation to use. There are 43 enums and the value can be one of: getVersions, getResources, getGlobalObjects, getBasicInfo, getDescription, getSObject, createSObject, updateSObject, deleteSObject, getSObjectWithId, upsertSObject, deleteSObjectWithId, getBlobField, query, queryMore, queryAll, search, apexCall, recent, createJob, getJob, closeJob, abortJob, createBatch, getBatch, getAllBatches, getRequest, getResults, createBatchQuery, getQueryResultIds, getQueryRe [...]
+| *operationName* | The operation to use. There are 59 enums and the value can be one of: getVersions, getResources, getGlobalObjects, getBasicInfo, getDescription, getSObject, createSObject, updateSObject, deleteSObject, getSObjectWithId, upsertSObject, deleteSObjectWithId, getBlobField, query, queryMore, queryAll, search, apexCall, recent, createJob, getJob, closeJob, abortJob, createBatch, getBatch, getAllBatches, getRequest, getResults, createBatchQuery, getQueryResultIds, getQueryRe [...]
 | *topicName* | The name of the topic/channel to use |  | String
 |===
 
 
-=== Query Parameters (44 parameters):
+=== Query Parameters (45 parameters):
 
 
 [width="100%",cols="2,5,^1,2",options="header"]
@@ -827,6 +863,7 @@ with the following path and query parameters:
 | *notifyForOperationUndelete* (common) | Notify for un-delete operation, defaults to false (API version = 29.0) |  | Boolean
 | *notifyForOperationUpdate* (common) | Notify for update operation, defaults to false (API version = 29.0) |  | Boolean
 | *objectMapper* (common) | Custom Jackson ObjectMapper to use when serializing/deserializing Salesforce objects. |  | ObjectMapper
+| *queryLocator* (common) | Query Locator provided by salesforce for use when a query results in more records than can be retrieved in a single call. Use this value in a subsequent call to retrieve additional records. |  | String
 | *rawPayload* (common) | Use raw payload String for request and response (either JSON or XML depending on format), instead of DTOs, false by default | false | boolean
 | *reportId* (common) | Salesforce1 Analytics report Id |  | String
 | *reportMetadata* (common) | Salesforce1 Analytics report metadata for filtering |  | ReportMetadata