Today we will check how we can use Bulk API 2.0 using Apex and Flow. Previously it was not possible to use HTTP PATCH request in Apex. Due to that we cannot use Bulk API 2.0 in apex. Finally in Salesforce Winter 21 release we can now use PATCH request in apex. If you haven’t checked the Winter 21 release notes you can quickly check them here. So today we will check how we can consume Bulk API in Apex. For UI we have used Flow, as we need simple File selection screen.

So this is how our Final UI will look like
As we can see in the demo, firstly we pass the object name and operation type and then we select the data file. then we pass all the details to controller and where we make actual request to upload the data and return the Job Id.
Now we will check the code of apex class.
BulkAPIUtility.Apxc
public class BulkAPIUtility {
@InvocableMethod(label='Upload CSV' description='Upload the CSV data in system using Bulk API 2.0')
public static List<ResponseWrapper> importBulkdata(List<BulkDataWrapper> dataWrapperList) {
ResponseWrapper rw = new ResponseWrapper();
if(dataWrapperList.size() == 0) {
rw.response = 'Please send correct data.';
}
String objectName = dataWrapperList[0].objectName;
String operation = dataWrapperList[0].operation;
String contentDocumentId = dataWrapperList[0].fileIdList[0];
ContentVersion cv = [SELECT Id, ContentDocumentId, VersionData FROM ContentVersion WHERE ContentDocumentId =: contentDocumentId ];
HttpRequest request = new HttpRequest();
request.setMethod('POST');
request.setEndpoint(URl.getOrgDomainUrl().toExternalForm()+'/services/data/v50.0/jobs/ingest');
request.setHeader('content-type', 'application/json' );
request.setHeader('Authorization' ,'Bearer '+userInfo.getSessionId() );
String body = '{ '+
'"externalIdFieldName": "Id",'+
'"lineEnding": "CRLF",'+
'"operation": "'+operation+'",'+
'"object": "'+objectName+'",'+
'"contentType": "CSV"'+
'}';
request.setBody(body);
Http h = new Http();
HttpResponse resp = h.send(request);
if(resp.getStatusCode() <= 299) {
Map<String, Object> respMap = (Map<String, Object>) Json.deserializeUntyped(resp.getBody());
String jobId = (String)respMap.get('id');
//second request to store data
HttpRequest request1 = new HttpRequest();
request1.setMethod('PUT');
request1.setEndpoint(URl.getOrgDomainUrl().toExternalForm()+'/services/data/v50.0/jobs/ingest/'+jobId+'/batches');
request1.setHeader('content-type', 'text/csv' );
request1.setHeader('Authorization' ,'Bearer '+userInfo.getSessionId() );
request1.setBody(cv.VersionData.toString());
Http h1 = new Http();
HttpResponse resp1 = h1.send(request1);
if(resp1.getStatusCode() <= 299) {
//third request to start processing
HttpRequest request2 = new HttpRequest();
request2.setMethod('PATCH');
request2.setEndpoint(URl.getOrgDomainUrl().toExternalForm()+'/services/data/v50.0/jobs/ingest/'+jobId);
request2.setHeader('content-type', 'application/json' );
request2.setHeader('Authorization' ,'Bearer '+userInfo.getSessionId() );
request2.setBody('{ "state" : "UploadComplete" }');
Http h2 = new Http();
HttpResponse resp2 = h2.send(request2);
//Delete the uploaded file as we no longer need that
Delete [SELECT ID FROM contentDocument WHERE ID =: contentDocumentId];
if(resp2.getStatusCode() <= 299) {
rw.response = 'Data processing Start '+jobId;
} else {
rw.response = 'There was an error. Please contact your admin.';
}
}
}
return new List<ResponseWrapper>{rw};
}
public class BulkDataWrapper {
@InvocableVariable(label='File Ids' description='File Ids' required=true)
public List<Id> fileIdList;
@InvocableVariable(label='Object Name' description='Object Name' required=true)
public String objectName;
@InvocableVariable(label='Operation' description='operation' required=true)
public String operation;
}
public class ResponseWrapper {
@InvocableVariable(label='Response' description='Response' required=true)
public String response;
}
}
So we have used InvocableMethod
method to use this method in Flow. Here we are making three request to consume Bulk API 2.0. Firstly we are passing the Object details and Operation type to initiate the process. Secondly we upload the data file for the bulk API operation. My file size is small so I have uploaded in single request you may need to divide them into chunk based on file size. Finally we make the UploadComplete request to let the API know file was uploaded success fully and it can start the process.Once the request was complete we delete the File as we no longer need that.
Important Notes:
- Previously PATCH was not supported in APEX but after Winter 21 release it is supported. So if you run this code in previous version you will get error.
- We have used
URl.getOrgDomainUrl()
to get the base URL. When we use this method we don’t need to create RemoteSiteSetting and we always get the org domain URL.
You can find the complete code with Flow here.
So did you like the post or do you have any questions, let me know in comments. Happy Programming 🙂
Hi ,
This is really helpful. I am facing one issue(401, Unauthorised in the first rest call) while am running the flow from application. However it is successful while i am running the flow in debug mode and from anononymous apex window.
Can you please advise
Thanks
Tanuja
I have the same problem. Did you find the solution?
Same Issue. Anybody found any resolution to this? Thanks
Are you geeting this error only first time or everytime you call this?
Status 401 Everytime.. but the same callout in Anonimous window works well
[{“message”:”This session is not valid for use with the REST API”,”errorCode”:”INVALID_SESSION_ID”}]
I did test it again today and it work fine for me. May be some security restriction in your org.