function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
Wole AdeyeyeWole Adeyeye 

Apex Batch : You have uncommitted work pending. Please commit or rollback before calling out

I have a batch class that makes a external Http callout to create case records with a CreatePublicStuffCases() which is called in the start(). In the execute function, I have another callout to get users details for specific cases that were previously inserted in the CreatePublicStuffCases(). Then at the end, do a DML to insert new account and also, update the cases that were previously inserted with thier respective case owners. I keep getting the error "System.CalloutException: You have uncommitted work pending. Please commit or rollback before calling out". Even when I tried doing the insert outside the loop with a List DML insert. Any help is much appreciated.

The start function : 
public Database.QueryLocator start(Database.BatchableContext context)
{
    CreatePublicStuffCases();
    String ps = 'Public Stuff';
    return Database.getQueryLocator('SELECT External_Source_Id__c FROM Case WHERE Requires_User_Details__c = true AND External_Source_Id__c != Null AND Origin =: ps');
}
The execute function :
public void execute(Database.BatchableContext context, List<Case> scope)
{

    //List<Account> publicStuffAccounts = new List<Account>();
    for (Case cse : scope)
    {            
        String newReq = cse.External_Source_Id__c;
        HttpRequest req = new HttpRequest();
        req.setMethod('GET');
        req.setEndpoint('https://www.publicstuff.com/api/2.0/request_view?request_id='+newReq');
        Http http = new Http();
        HTTPResponse res = http.send(req);

        String resonseBody = res.getBody();
        String jsonString = resonseBody;
        Map<String,Object> rawObj = (Map<String,Object>) JSON.deserializeUntyped(jsonString);

        Map<String,Object> responseObj = (Map<String,Object>)rawObj.get('response');
        Map<String,Object> userDetails = (Map<String,Object>) responseObj.get('user_detail');

        //select a specific value
        String requeststatus = (String) responseObj.get('request_status');
        String firstname = (String) userDetails.get('firstname');
        String lastname = (String) userDetails.get('lastname');
        String email = (String) userDetails.get('email');
        String phone = (String) userDetails.get('phone');
        String address = (String) userDetails.get('address');
        String zipcode = (String) userDetails.get('zipcode');
        String space = (String) userDetails.get('space');
        String state = (String) userDetails.get('state');

        //check
        Account newPublicStuffContact = new Account(LastName = lastname, FirstName = firstname, PersonEmail = email, Phone = phone, PersonMailingStreet = address, PersonMailingPostalCode = zipcode,
            PersonMailingState = space, PersonMailingCountry = state);
        if (lastname == Null){
            return;
        }
        else{
            try{
                insert newPublicStuffContact;
                //publicStuffAccounts.add(newPublicStuffContact);
                Case caseUpdate = [SELECT Id, ContactId, Requires_User_Details__c FROM Case WHERE Id = :cse.Id];
                //update and mark the case's Requires_User_Details__c as false
                caseUpdate.Requires_User_Details__c = false;
                caseUpdate.AccountId = newPublicStuffContact.Id;
                System.debug('The newPublicStuffContact Id : ' + newPublicStuffContact.Id);
                Update caseUpdate;
                }
                catch (Exception e){
                    System.debug('Error inserting case' + e.getMessage())
                }
        }
        //mapPSContact.put(newPublicStuffContact.Id, newPublicStuffContact);
    }
    //insert publicStuffAccounts;
}
The CreatePublicStuffCases function : 
public static void CreatePublicStuffCases()
{
    // Callout code verbatim from the question
    Date d = Date.today();
    JSONGenerator gen = JSON.createGenerator(true);

    HttpRequest req = new HttpRequest();
    req.setMethod('GET');
    req.setEndpoint('https://www.publicstuff.com/api/2.0/requests_list?limit=2&client_id=****&status=open');
    Http http = new Http();
    HTTPResponse res = http.send(req);
    String resonseBody = res.getBody();
    String jsonString = resonseBody;
    Map<String,Object> rawObj = (Map<String,Object>) JSON.deserializeUntyped(jsonString);
    Map<String,Object> responseObj = (Map<String,Object>)rawObj.get('response');
    List<Case> newCases = new List<Case>();
    Map<String, Object> iMap = new Map<String, Object> ();
    Map<String,Object> id = new Map<String, Object>();
    List<Object> reqs = (List<Object>)responseObj.get('requests');
    for (Object x : reqs)
    {
        iMap = (Map<String, Object>)x;
        for (String field : iMap.keySet())
        {
            id = (Map<String,Object>) iMap.get(field);                
            String idx = 'id';
            String title = 'title';
            String description = 'description';
            String status = 'status';
            String address = 'address';
            String location = 'location';
            String zipcode = 'address';

            Integer OId = (Integer) id.get(idx);
            String Otitle = (String) id.get(title);
            String Odescription = (String) id.get(description);
            String OStatus = (String) id.get(status);
            String Oaddress = (String) id.get(address);
            String OLocation = (String) id.get(location);
            String OZipCode = (String) id.get(zipcode);
            Boolean OrequiresUserDetails = true;

                String CaseRecordTypeId = GlobalStaticMetaDataCache.getRTId('Case', GlobalFixedParams.RECORDTYPE_CASE_SERVICE_REQUEST);
                Case c = new Case(recordTypeId = CaseRecordTypeId, External_Source_Id__c = String.valueOf(OId), Customer_Request_Details__c = Otitle + ' ' + 
                    Odescription, Status = OStatus, SuppliedTargetAddress__c = Oaddress + ' ' + OLocation + ' ' + OZipCode, Requires_User_Details__c = OrequiresUserDetails, Origin = 'Public Stuff');
                newCases.add(c);
        }
    }
    try{
            insert newCases;
    }catch (Exception e){
    }
}
The excute function will send an email but that is no problem at the moment.
 
Roy LuoRoy Luo
In the constructor cases are created, but not fully commited to the db yet when execute runs. In excute method, there is a httprequest call out that surely will give you the error. 
 
public class MyBatchable implements Database.Batchable<sObject>
{
	public MyBatchable(List<Case> cases)
	{
		caseObjs = cases;
	}
	List<Case> caseObjs {get; set;}	

	public Database.QueryLocator start(Database.BatchableContext context)
	{
	    //CreatePublicStuffCases();
	    String ps = 'Public Stuff';
	    return Database.getQueryLocator('SELECT External_Source_Id__c FROM Case WHERE Requires_User_Details__c = true AND External_Source_Id__c != Null AND Origin =: ps');
	}
       
      public static List<Case> CreatePublicStuffCases()
      {
          ...
           return cases;
       }
​
}

Then kick off the batch job as
Database.execute(new MyBatchable(MyBatchable.CreatePublicStuffCases()), 200);



 
Wole AdeyeyeWole Adeyeye
@RoyLuo, the problem is not with creating the cases but with the execute. The cases are getting created correctly but because it submits itself again straight after case creating, this is stopping the code in the execute to fire another callout