Salesforce Batch Apex Variables
I've been reading a lot of questions on the developer boards recently regarding the use of variables within batch Apex classes. One post was asking how to pass a variable to the execute method of a batch class. Another asked about variable accessibility in the start, execute & finish methods of batch.
The purpose of this post is to show some of the ways that variables work within batch apex classes.
The code we will use doesn't really do anything. But it will work in your Org. Simply copy and paste it as a new Apex Class.
/*
Created by: Greg Hacic
Last Update: 13 August 2019 by Greg Hacic
Questions?: greg@ities.co
Notes:
- example of batch apex variables
- want to run this in your developer org or sandbox?
- open the developer console in your salesforce Org
- Execute Anonymous
batchExample b = new batchExample('No Contact'); //new instance of batchExample
Id batchProcessId = Database.executeBatch(b, 3); //submits the batchExample batch Apex job for execution with a batch size of 3
*/
public class batchExample implements Database.Batchable<SObject>, Database.Stateful {
private final String finalStringVariable; //final instance variable
private String stringVariable; //instance variable
private static String staticStringVariable; //static variable
private Set<String> setOfContactNames = new Set<String>(); //set of Contact.Name
private static Set<String> staticSetOfContactNames = new Set<String>(); //set of Contact.Name
//constructor
public batchExample(String passedString) {
System.debug('- Constructor: Begin -');
finalStringVariable = passedString; //assign the passed string to finalStringVariable
stringVariable = passedString; //assign the passed string to stringVariable
staticStringVariable = passedString; //assign the passed string to staticStringVariable
System.debug('finalStringVariable: '+finalStringVariable+' | stringVariable: '+stringVariable+' | staticStringVariable: '+staticStringVariable);
System.debug('setOfContactNames.size(): '+setOfContactNames.size()+' | staticSetOfContactNames.size(): '+staticSetOfContactNames.size());
System.debug('- Constructor: End -');
}
//the "start" method is called at the beginning of a batch Apex job
//use this method to collect the records (of objects) to be passed to the "execute" method for processing
public Database.QueryLocator start(Database.BatchableContext bc) {
System.debug('- start: Begin -');
System.debug('finalStringVariable: '+finalStringVariable+' | stringVariable: '+stringVariable+' | staticStringVariable: '+staticStringVariable);
System.debug('setOfContactNames.size(): '+setOfContactNames.size()+' | staticSetOfContactNames.size(): '+staticSetOfContactNames.size());
System.debug('- start: End -');
return Database.getQueryLocator('SELECT Id, Name FROM Contact LIMIT 10'); //return the query locator
}
//the "execute" method is called after the "start" method has been invoked and passed a batch of records
public void execute(Database.BatchableContext bc, List<SObject> scope) {
System.debug('- execute: Begin -');
for (SObject s : scope) { //for all sObjects in the batch
Contact c = (Contact)s; //cast the Contact object from the scope
//Final members can only be assigned in their declaration, init blocks, or constructors so the variable cannot be updated here: finalStringVariable = c.Name; //assign the Contact.Name to finalStringVariable
stringVariable = c.Name; //assign the Contact.Name to stringVariable
staticStringVariable = c.Name; //assign the Contact.Name to staticStringVariable
System.debug('finalStringVariable: '+finalStringVariable+' | stringVariable: '+stringVariable+' | staticStringVariable: '+staticStringVariable);
setOfContactNames.add(c.Name); //add the Contact Name to the set
staticSetOfContactNames.add(c.Name); //add the Contact Name to the set
System.debug('setOfContactNames.size(): '+setOfContactNames.size()+' | staticSetOfContactNames.size(): '+staticSetOfContactNames.size());
}
System.debug('- execute: End -');
}
//the "finish" method is called once all the batches are processed
public void finish(Database.BatchableContext bc) {
System.debug('- finish: Begin -');
System.debug('finalStringVariable: '+finalStringVariable+' | stringVariable: '+stringVariable+' | staticStringVariable: '+staticStringVariable);
System.debug('setOfContactNames.size(): '+setOfContactNames.size()+' | staticSetOfContactNames.size(): '+staticSetOfContactNames.size());
System.debug('setOfContactNames: '+setOfContactNames);
System.debug('staticSetOfContactNames: '+staticSetOfContactNames);
System.debug('- finish: End -');
}
}
We are iterating over all the Contacts in the Org and creating some variables with the data then displaying the values in the debug log. I think that is an easy way to illustrate how the values change and update as batches are processed.
Once your Apex class has been saved you can open the developer console and then open the Execute Anonymous Window (Within the console click Debug > Open Execute Anonymous Window).
Now execute the batch job by copying and pasting the following syntax into the window:
batchExample b = new batchExample('No Contact'); //new instance of batchExample
Id batchProcessId = Database.executeBatch(b, 3); //submits the batchExample batch Apex job for execution with a batch size of 3
Highlight the two lines and click the button reaching "Execute Highlighted".
Click the Logs tab located in the bottom section of the developer console. You will see a series of log entries that show the various actions being taken by the batch class. Double click any of the log rows to open them. Then click the checkbox next to the text "Debug Only" to see the debug statements from our Apex class.
The logic we wrote forced the batch to process three records at a time. Since we limited the SOQL statement to only grab ten records we should see one log entry for the start method, one log for the constructor, four logs for each of the batches that was processed and a log for the finish method. Don't worry about the number of logs too much. My point is that you won't see everything in one log record. I took some time to grab the debugs for my sandbox and displayed them below:
[24]|DEBUG|- Constructor: Begin -
[28]|DEBUG|finalStringVariable: No Contact | stringVariable: No Contact | staticStringVariable: No Contact
[29]|DEBUG|setOfContactNames.size(): 0 | staticSetOfContactNames.size(): 0
[30]|DEBUG|- Constructor: End -
[36]|DEBUG|- start: Begin -
[37]|DEBUG|finalStringVariable: No Contact | stringVariable: No Contact | staticStringVariable: No Contact
[38]|DEBUG|setOfContactNames.size(): 0 | staticSetOfContactNames.size(): 0
[39]|DEBUG|- start: End -
[45]|DEBUG|- execute: Begin -
[51]|DEBUG|finalStringVariable: No Contact | stringVariable: John Bond | staticStringVariable: John Bond
[54]|DEBUG|setOfContactNames.size(): 1 | staticSetOfContactNames.size(): 1
[51]|DEBUG|finalStringVariable: No Contact | stringVariable: Edna Frank | staticStringVariable: Edna Frank
[54]|DEBUG|setOfContactNames.size(): 2 | staticSetOfContactNames.size(): 2
[51]|DEBUG|finalStringVariable: No Contact | stringVariable: Avi Green | staticStringVariable: Avi Green
[54]|DEBUG|setOfContactNames.size(): 3 | staticSetOfContactNames.size(): 3
[56]|DEBUG|- execute: End -
[45]|DEBUG|- execute: Begin -
[51]|DEBUG|finalStringVariable: No Contact | stringVariable: Stella Pavlova | staticStringVariable: Stella Pavlova
[54]|DEBUG|setOfContactNames.size(): 4 | staticSetOfContactNames.size(): 1
[51]|DEBUG|finalStringVariable: No Contact | stringVariable: Lauren Boyle | staticStringVariable: Lauren Boyle
[54]|DEBUG|setOfContactNames.size(): 5 | staticSetOfContactNames.size(): 2
[51]|DEBUG|finalStringVariable: No Contact | stringVariable: Tess Dachshund | staticStringVariable: Tess Dachshund
[54]|DEBUG|setOfContactNames.size(): 6 | staticSetOfContactNames.size(): 3
[56]|DEBUG|- execute: End -
[45]|DEBUG|- execute: Begin -
[51]|DEBUG|finalStringVariable: No Contact | stringVariable: Jack Rogers | staticStringVariable: Jack Rogers
[54]|DEBUG|setOfContactNames.size(): 7 | staticSetOfContactNames.size(): 1
[51]|DEBUG|finalStringVariable: No Contact | stringVariable: Babara Levy | staticStringVariable: Babara Levy
[54]|DEBUG|setOfContactNames.size(): 8 | staticSetOfContactNames.size(): 2
[51]|DEBUG|finalStringVariable: No Contact | stringVariable: Jane Grey | staticStringVariable: Jane Grey
[54]|DEBUG|setOfContactNames.size(): 9 | staticSetOfContactNames.size(): 3
[56]|DEBUG|- execute: End -
[45]|DEBUG|- execute: Begin -
[51]|DEBUG|finalStringVariable: No Contact | stringVariable: Jake Llorrac | staticStringVariable: Jake Llorrac
[54]|DEBUG|setOfContactNames.size(): 10 | staticSetOfContactNames.size(): 1
[56]|DEBUG|- execute: End -
[61]|DEBUG|- finish: Begin -
[62]|DEBUG|finalStringVariable: No Contact | stringVariable: Jake Llorrac | staticStringVariable: null
[63]|DEBUG|setOfContactNames.size(): 10 | staticSetOfContactNames.size(): 0
[64]|DEBUG|setOfContactNames: {Avi Green, Babara Levy, Edna Frank, Jack Rogers, Jake Llorrac, Jane Grey, John Bond, Lauren Boyle, Stella Pavlova, Tess Dachshund}
[65]|DEBUG|staticSetOfContactNames: {}
[66]|DEBUG|- finish: End -
As you can see from the logs, the static variables get reset for each batch. However, the instance variables are visible and carry over from batch to batch. Anything declared as final can only be assigned in their declaration, init blocks or constructors.
In most circumstances you would not need to use the Database.Stateful declaration in your batch classes. I just wanted to illustrate to other developers/admins what's going on with the variables in the class when you do this.