+ Start a Discussion
Eric Blaxton 11Eric Blaxton 11 

Database.executeBatch cannot be called from a batch start, batch execute, or future method

Hi and thanks in advance for any guidance.  

Background:  This code works great in getting a JSON response and creating cases.  The issues come when I schedule the class, as shown in my code snippet below, 'Class global class MaintenanceRequest_Sch implements Schedulable '.  It's calling the Asynchronous method in 'Maint. Request class.

Here's my code snippets:
 
public class MaintenanceRequest
{   
    // need this for scheduling callouts.
    @future(callout=true)
    public static void getMaintenanceTicketsAsync() 
    {
        getMaintenanceTickets();
    }
    
    public static void getMaintenanceTickets()
    {
        // Call public class ApiBearerKey to get new bearer key
        String tokenkey = ApiBearerKey.getBearerKey(); 
       
        // use Custom Settings in the future 
        String prdEndpoint = system.label.Portal_Prod_Endpoint;
        
        //Date variables for Endpoint
        //Put these in Custom settings, so I can adjust without redeploying
        Date d1= Date.today()-30;
        String geDate = DateTime.newInstance(d1.year(),d1.month(),d1.day()).format('YYYY-MM-dd');
        Date d2 = Date.today();
        String leDate = DateTime.newInstance(d2.year(),d2.month(),d2.day()).format('YYYY-MM-dd');
        String Jan12021 = '2021-01-01';//users only want to load cases with report date 1/1/2021 
        //Variable created to exclude all Cases with Short Description contains "Variance"
        String exclude1 = '\'Variance\'';
                         
        HTTP h = new HTTP();
        HTTPRequest req = new HTTPRequest();   
       
        //Odata query.  We can get this running once Modified date is populated. 
        //req.setEndpoint(prdEndpoint + '?$filter=ODATA quere sample
        req.setTimeout(120000);       
        req.setMethod('GET'); 
        req.setHeader('Authorization', 'Bearer ' + tokenkey); 
                
        HTTPResponse res = h.send(req);   
        // This call returns 1.43 (1443502) MB.  Using Modified dates for 10 days
        Blob reqBody = res.getBodyAsBlob();
        system.debug('getsize ' + reqbody.size());
        system.debug('getsize ' + res);
        
        String strResponse = res.getBody();  
                
        if(res.getstatusCode() == 200 && res.getbody() != null)
            //system.debug('Response: ' + res.getBody());
            
        {           
            Map<String,Object> newMap = (Map<String, Object>)JSON.deserializeUntyped(strResponse); //strResponse gets top level and lower level details            
            //List of records passed.
            List<Object > valuelist = (List<Object>) newMap.get('value');
      
         createMaintCase.upsertCases(valuelist); 
}
}
global class MaintenanceRequest_Sch implements Schedulable {

    public void execute(SchedulableContext context)
    {
        MaintenanceRequest.getMaintenanceTicketsAsync();
    }
    
}
public class createMaintCase
{ 
    //List of Cases passed in from MaintenanceRequest.apxc
    //public static List<String> upsertCases(List <Object> caseList)
        
    public static void upsertCases(List <Object> caseList)
    {
        //system.debug('Case List ' + caseList);
        // Instantiate new list
        List<Case> toBeUpsertCases = new List<Case>();
        
        //List holding Maint Request Id's
        List<String> ticketIds = new List<String>();
        
        //List holding ticketIds, not to be confused with ticketId which holds maintrequestid's
        //List<String> listOfTicketIds = new List<String>();
        //
       Id caseMaintRecordTypeId = Schema.Sobjecttype.Case.getRecordTypeInfosByDeveloperName().get('SAP_Cases').getRecordTypeId();
        
        // Loop through list and load up received values in List toBeUpsertedCases
        Set<String> setReferenceId = new Set<String>();
        List<String> allids = new List<String>();
        system.debug('1st Case List: ' + caseList.size() );
        for(Object obj: caseList)
        {
            Map<String,Object> valueMap = (Map<String, Object> )obj; // all Values            
            
            Map<String,Object> locdetails  = (Map<String, Object>)valueMap.get('location'); // Location details            
            
            if(null != locdetails)
            {    
                // add Null check because some location details don't have a SAP Reference Id
                if (null != locdetails.get('sapReferenceId'))
                {
                    String sapReferenceId= String.valueOf(locdetails.get('sapReferenceId')); 
                                                   
                    if(null != sapReferenceId)
                     {
                      setReferenceId.add(sapReferenceId); // Load up sapReference Id's in Set
                      allids.add(sapReferenceId); // I load a map of all id's, but debug is forcefully truncated by SF.  Will need to store in an object or write to a file.
                     } 
                } 
            }
        } 
           
        if(!setReferenceId.isEmpty())
        {
            // put variables into custom setting.  to save from redeploying.  custom setting will allow
            //  me to vary sizes for testing.
            if (setReferenceId.size() > 25) {
                system.debug('setReferenceId.size: ' + setReferenceId.size());
                system.debug('caseList.size: ' + caseList.size());
				Database.executeBatch(new CreateMaintCase_Batch(setReferenceId, caseList), 25);
				return;                
            }
            
            //Fetching the assignment rules on case
               AssignmentRule AR = new AssignmentRule();
            // Get Case Active assignment rule
                AR = [Select id from AssignmentRule where SobjectType = 'Case' and Active = true limit 1];
                //Creating the DMLOptions for "Assign using active assignment rules" checkbox
                Database.DMLOptions dmlOpts = new Database.DMLOptions();
                dmlOpts.assignmentRuleHeader.assignmentRuleId= AR.id;
            
            Map<String,Account> mapReferenceIdVsAccount = new Map<String, Account>();
            //Map<String,Account> mapRefIdNotFound = new Map<String, Account>();
           //Load SAP Reference ID (Bus Loc) into Map
            for(Account acc: [Select Id, Name, AccountNumber From Account Where AccountNumber IN: setReferenceId])
            {              
                mapReferenceIdVsAccount.put(acc.AccountNumber , acc);
            }
                                  
            //System.debug('### mapReferenceIdVsAccount = '+ mapReferenceIdVsAccount);
            if(!mapReferenceIdVsAccount.isEmpty())
            {
                //This is where we pass caseList to batch class CreateMaintCase_Batch
                for (Object obj : caseList )
                {  
                    Map<String,Object> valueMap = (Map<String, Object> )obj;               
                    string ticketid= String.valueOf(valueMap.get('ticketId')); 
                    string shortDescription= String.valueOf(valueMap.get('shortDescription'));
                    string ticketNumber = String.valueOf(valueMap.get('ticketNumber')); 
                    string longDescription= String.valueOf(valueMap.get('longDescription'));                                      
                    Datetime reportDate; 
                    Datetime statusDate;
                    Datetime closeDate;
                    
                   //Create cases from JSON values
                        // Loop through and add case to List
                        toBeUpsertCases.add(newcase);
                        
                    }  //end if(mapReferenceIdVsAccount.containsKey(sapReferenceId)) 
                  }//end if (null != String.valueOf(locdetails.get('sapReferenceId')))
                  }
                }// end for (Object obj : caseList )
            }
        }    
        // Upsert Maint Request cases
        //system.debug('toBeUpsertCases: ' + toBeUpsertCases.size() );
        Schema.SObjectField f = Case.Ticket_ID__c; 
        if(!toBeUpsertCases.isEmpty()){
            Database.UpsertResult [] cr = Database.upsert(toBeUpsertCases, f, false);
            system.debug('CR from create maintenance case : ' + cr.size());
           
                             
    }// end public static void upsertCases(List <Object> caseList)
}// end public class createMaintCase
global class CreateMaintCase_Batch implements Database.Batchable<sObject>, Database.Stateful
{
    List<Case> toBeUpsertCases; 
    
    //Fetching the assignment rules on case
     AssignmentRule AR = new AssignmentRule();     
    
    string query;
    Set<String> setReferenceId;
    List <Object> caseList;
    String emailBody;
    Integer numberOfFailedCases;
    global CreateMaintCase_Batch(Set<String> setReferenceId, List <Object> caseList)
    {
        this.setReferenceId = setReferenceId;
        this.caseList = caseList;
        //Create Query
        query = 'Select Id, Name, AccountNumber From Account Where AccountNumber IN :setReferenceId';
        system.debug('Query ' + query);
        toBeUpsertCases = new List<Case>();
        emailBody = 'Failed Cases\n';
        numberOfFailedCases = 0;
    }
    
    //Makes it batchable
    global Database.QueryLocator start(Database.BatchableContext bc)
    {
        return Database.getQueryLocator(query);
    }
    
    //Execute code
    global void execute(Database.BatchableContext bc, List<Object> accounts)
    {
        // Get Case Active assignment rule
        AR = [Select id from AssignmentRule where SobjectType = 'Case' and Active = true limit 1];
        //Creating the DMLOptions for "Assign using active assignment rules" checkbox
        Database.DMLOptions dmlOpts = new Database.DMLOptions();
        dmlOpts.assignmentRuleHeader.assignmentRuleId= AR.id;
        
        Map<String, Account> mapReferenceIdVsAccount = new Map<String, Account>();
        for(Account acc: (List<Account>)accounts)
        {              
                mapReferenceIdVsAccount.put(acc.AccountNumber , acc);
        }
        
        Id caseMaintRecordTypeId = Schema.Sobjecttype.Case.getRecordTypeInfosByDeveloperName().get('SAP_Cases').getRecordTypeId();
       
                  //Populate cases off Json values

                        // Loop through and add case to List
                        toBeUpsertCases.add(newcase);
                        processedSapIds.add(sapReferenceId);
                        
                        // Stop once list sizes don't match
                        if (processedSapIds.size() == mapReferenceIdVsAccount.size()) {
                            break;
                        }
                    }  //end if(mapReferenceIdVsAccount.containsKey(sapReferenceId)) 
                  }//end if (null != String.valueOf(locdetails.get('sapReferenceId')))
                  }
                }// end for (Object obj : caseList )
        
        Schema.SObjectField f = Case.Ticket_ID__c; // This defines Ticket_Number__c as the External ID key
        system.debug('Before upsert: ' + tobeUpsertCases.size());
        if(!toBeUpsertCases.isEmpty()){
            Database.UpsertResult [] cr = Database.upsert(toBeUpsertCases, f, false);
            //Clear list before next batch processes
            tobeUpsertCases.clear();
            system.debug('After upsert: ' + tobeUpsertCases.size());
            
        
    }
    
    //Finish the Batch
    global void finish(Database.BatchableContext bc)
    {
        
    }
}





 
AnudeepAnudeep (Salesforce Developers) 
This error occurs in general if we execute a batch class and because of its execution, a trigger is called which again executes a batch class, so although indirectly but still we are calling a batch class from another batch class(and not from its Finish method) which is not allowed.

I recommend reviewing the following resources to learn more

https://techemizen.medium.com/how-to-invoke-batch-apex-from-another-batch-apex-in-salesforce-1a7148a6b078

https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_batch_interface.htm

Let me know if this helps
Eric Blaxton 11Eric Blaxton 11
Hi Anudeep,

Appreciate your time.

I know the reason it's happening.  The articles you sent don't apply to my situation as I am not trying to call one batch from another.  The issue comes in because of the @future method I am using coupled with the database.executebatch.  My design is flawed.  Do you have any advice to overcome this obstacle?  

1. Could using queable class help solve my problem, which is not being able to Schedule the batch?
2. Do I need to redesign?  One idea is to push all the data into a temp object and then create my cases as a Batch.

Regards,
Eric