What Are Future Methods in Salesforce

ליאור נכתב על ידי ליאור לביא, עודכן בתאריך 27/10/2023

Let's start with the dry definition for future methods: Future methods are methods that run in a separate thread of their own, at an undefined time in the future, and have higher governor limits.

Now, in order to understand what future methods are, let's break down the previous sentence and understand what it means exactly.

  1. "Future methods are methods that run in a separate thread of their own."

    In other words, a future method runs in a separate transaction, in a separate execution context. This fact allows us to use future methods to avoid Mixed DML Operation errors.

  2. "At an undefined time in the future."

    Future methods do not run immediately after they are called, but they are placed in a queue on the server. When their turn comes, depending on the availability of server resources, they will execute. This behavior is called "asynchronous execution." For more information on asynchronous code execution, click here.

  3. "And have higher governor limits."

    Every activity against Salesforce servers is subject to a set of limits whose role is to ensure that server resources are available to all users all the time, so that one user does not dominate a significant portion of the system's resources unfairly. However, Salesforce allocates a larger pool of resources for use with future methods. Therefore, if we have code that requires a high amount of resources, and the business scenario allows it, we can use future methods to run the logic asynchronously, enabling the platform to allocate us more resources.

When to Use Future Methods

The common use cases for using future methods are:

  1. Calling External Services - Calls to external services, such as web service APIs, must be made using future methods or queueable Apex when they are invoked from a trigger or after a DML operation. The reason for this is that if we try to make an external service call from a trigger, our connection to the database will need to remain open while waiting for a response from the external service, which is forbidden in a multi-user environment like Salesforce.
  2. Running Resource-Intensive Processes - If the business process allows it, sometimes we'll want to use future methods to run processes that take a long time or require many queries or database operations, as they'll have higher governor limits.
  3. Running a Method in a Separate Transaction to Avoid Mixed DML Operation Error - Sometimes we want to execute logic that requires DML operations on both Setup and Non-Setup objects, such as User and Account, but performing these actions in the same execution context is not allowed in Salesforce. The fact that future methods run in their own thread in the future allows us to split the logic into two parts: a synchronous method that updates one type of object, such as Setup objects like User, and an asynchronous method that updates only Non-Setup records like Account, for example.

How to Write Future Methods

The structure of future methods is quite simple and relies on annotating a method with the @future tag:

public class myCoolClass {
    @future
    public static void myAwesomeFutureMethod(List<Id> accountsIDsList) {
        List<Account> myAccounts = [SELECT Name FROM Account WHERE ID IN :accountsIDsList];
        for(Account singleAccount : myAccounts) {
            // Run some awesome code here...
        }
    }
}

Example of Making a Callout Using a Future Method

In the following example, we make a call to the Send method in a sample class called JIRAWebService, which makes a Web Service call to create an issue in the JIRA system.

public class JIRAConnector {
    @future(callout = true)
    public static void createIssueInJIRA(Id myRecordID) {
        // Invoking the Send method in the JIRAWebService class will result in a callout
        String response = JIRAWebService.send();
        
        // Get the record that fired the callout and update it with the key of the issue created in JIRA
        MyCustomObject__c myCustomObjectRecord = [SELECT Id, Key__c FROM MyCustomObject__c WHERE Id = :myRecordID LIMIT 1];
        myCustomObjectRecord.Key__c = response.get('key');
        Update myCustomObjectRecord;
        System.debug(response);
    }
}

Writing a Test Method for Testing Future Methods

Writing unit tests for future methods in Salesforce is based on two principles:

  1. Using a Mock response class - Test methods in Salesforce do not actually make calls to external services. Instead, Salesforce allows us to define special classes that mimic an external service and return a response in the appropriate format.
  2. Using test.startTest() and test.stopTest() methods to collect and run all asynchronous calls in a synchronous manner.

Writing a Mock Response Class

In the following example, we'll write a class that returns two types of responses, one for success and one for failure. This makes it easier to write test methods to check the behavior of our code in both scenarios. We pass a parameter to the class constructor to specify which type of response to return.

@IsTest
public class JIRAMockResponse implements HttpCalloutMock {
    
    // Best practice: Always make your properties private so access will only be made through public methods.
    private Boolean success;
    
    public JIRAMockResponse(Boolean success) {
        // Use the 'this' tag to initialize the success property of our class's instances from the parameter we passed to the constructor.
        this.success = success;
    }
    
    public HTTPResponse respond(HttpRequest req) {
        HttpResponse res = new HttpResponse();
        if (this.success) {
            res.setHeader('Content-Type', 'application/json');
            res.setBody('{"id":"001", "key":"PRJ-001"}');
            res.setStatusCode(200);
        } else {
            res.setHeader('Content-Type', 'application/json');
            res.setBody('{"errorMessages":[], "errors":{"customfield_001":"This is an error message."}}');
            res.setStatusCode(200);
        }
        
        return res;
    }
}

Example of Writing Unit Test for the createIssueInJIRA Method

@IsTest
private class JIRAConnector_Tests {
    
    @IsTest
    private static void createIssueInJIRA_Success() {
        // Note: We are passing the value 'true' to our mock class constructor.
        Test.setMock(HttpCalloutMock.class, new JIRAMockResponse(true));
        
        Test.startTest();
        
        JIRAConnector.createIssueInJIRA('abc123456789');
        
        // Note: This command will force the execution of our async future methods right now, and not in the future.
        Test.stopTest();
        
        // Let's get our initial record and see if the mock response key was saved to it.
        MyCustomObject__c myCustomObjectRecord = [SELECT Id, Key__c FROM MyCustomObject__c WHERE Id = :myRecordID LIMIT 1];
        System.assertEquals('PRJ-001', myCustomObjectRecord.key);
    }
}

Important Points to Consider When Writing Future Methods

In order to write future methods, it is important to follow certain rules:

  • Future methods must always be written as static.
  • Future methods must always return void.
  • Future methods can accept only three types of arguments:
    • Simple data types (String, Integer, and Id, for example).
    • Collections of simple data types (lists, maps, and sets).
    • Arrays of simple data types.
  • Future methods will not run in the order they were called. If two future methods are called on the same record, for example, we will not be able to determine which one will run first, which may result in record locking and error messages.
  • You cannot invoke a future method from another future method, which means you should be careful not to create a situation where updating a record from a future method triggers another future method.
  • There is a platform limit of 50 future methods per transaction, and in addition, there is a daily limit on the execution of asynchronous code (batch, future, queueable, and scheduled) of 250,000 or 200 times the number of user licenses in your organization, whichever is greater.