Salesforce Developer – Future Methods Questions

Future methods provide a simple way to run Apex code asynchronously, particularly useful for callouts and operations that need to bypass the current transaction. These questions cover future method implementation, callout handling, parameter limitations, error management, monitoring execution, testing strategies, governor limit considerations, best practices, comparison with other async options, complex scenarios, performance optimization, and integration patterns. Understanding future methods is essential for building responsive Salesforce applications.

Future Methods - Q&A

  1. Q1. What are future methods and when would you use them?
    Ans: Future methods are asynchronous Apex methods that: - Run in a separate thread from the calling transaction - Execute when system resources are available - Are useful for callouts to external services - Can bypass mixed DML operation restrictions Use cases: - Making callouts to external web services - Performing operations that need to bypass transaction limits - Handling mixed DML operations (user/setup objects) - Offloading long-running processes - Sending emails asynchronously
  2. Q2. How do you implement a future method?
    Ans: Implementation requirements: 1. Method must be static 2. Must have @future annotation 3. Parameters limited to primitives, arrays of primitives, or collections 4. Return type must be void Example:
    public class AccountService {
      @future
      public static void updateAccountStatus(Set accountIds) {
        List accounts = [SELECT Id, Status__c FROM Account WHERE Id IN :accountIds];
        for(Account acc : accounts) {
          acc.Status__c = 'Processed';
        }
        update accounts;
      }
      
      @future(callout=true)
      public static void makeCallout(String endpoint, String data) {
        Http http = new Http();
        HttpRequest request = new HttpRequest();
        request.setEndpoint(endpoint);
        request.setMethod('POST');
        request.setBody(data);
        HttpResponse response = http.send(request);
        // Process response
      }
    }
    
    // Calling future methods:
    Set accountIds = new Set{'001XXXXXXXXXXXXXXXXX', '001YYYYYYYYYYYYYYYYY'};
    AccountService.updateAccountStatus(accountIds);
  3. Q3. What are the limitations of future method parameters?
    Ans: Parameter limitations: - Allowed: Primitives (Integer, String, Boolean, etc.), arrays of primitives, collections of primitives - Not allowed: SObjects, custom objects, complex data structures - Workaround: Pass IDs and query data in future method Example of valid parameters:
    @future
    public static void processAccounts(Set accountIds, List statusValues, Map config) {
      // Valid parameters
      List accounts = [SELECT Id, Name FROM Account WHERE Id IN :accountIds];
      // Process accounts with statusValues and config
    }
    Example of invalid parameters:
    @future
    public static void processAccounts(List accounts) {
      // This will cause a compile error
      // SObjects cannot be passed to future methods
    }
  4. Q4. How do you handle callouts in future methods?
    Ans: Callout handling requirements: - @future(callout=true) annotation required - Timeout handling: Default 10 seconds - Error handling: Try-catch for network issues - Authentication: Secure token management Example:
    @future(callout=true)
    public static void syncWithExternalSystem(Set accountIds) {
      try {
        // Get account data
        List accounts = [SELECT Id, Name, Type FROM Account WHERE Id IN :accountIds];
        
        // Prepare data for external system
        List externalAccounts = new List();
        for(Account acc : accounts) {
          externalAccounts.add(new ExternalAccount(acc.Name, acc.Type));
        }
        
        // Make callout
        Http http = new Http();
        HttpRequest request = new HttpRequest();
        request.setEndpoint('https://api.external.com/sync');
        request.setMethod('POST');
        request.setHeader('Content-Type', 'application/json');
        request.setBody(JSON.serialize(externalAccounts));
        
        HttpResponse response = http.send(request);
        
        if(response.getStatusCode() == 200) {
          // Process success
          processSuccessResponse(response.getBody());
        } else {
          // Handle error
          handleError(response.getStatusCode(), response.getBody());
        }
      } catch(Exception e) {
        // Log error
        System.debug('Callout failed: ' + e.getMessage());
      }
    }
  5. Q5. How do you handle errors in future methods?
    Ans: Error handling approaches: - Try-catch blocks for exception handling - Logging errors to custom objects - Retry mechanisms for transient failures - Notification systems for critical errors Example:
    @future
    public static void processAccounts(Set accountIds) {
      try {
        List accounts = [SELECT Id, Name, Status__c FROM Account WHERE Id IN :accountIds];
        
        for(Account acc : accounts) {
          // Process account
          processAccount(acc);
        }
        
        update accounts;
      } catch(DmlException e) {
        // Handle DML errors
        logError('DML Error', e.getMessage(), accountIds);
      } catch(Exception e) {
        // Handle other errors
        logError('General Error', e.getMessage(), accountIds);
        
        // Retry if appropriate
        if(shouldRetry(e)) {
          // Note: Cannot directly retry future methods
          // Would need to trigger re-execution from calling context
        }
      }
    }
    
    private static void logError(String errorType, String message, Set accountIds) {
      Error_Log__c errorLog = new Error_Log__c(
        Error_Type__c = errorType,
        Message__c = message,
        Related_Ids__c = String.join(new List(accountIds), ',')
      );
      insert errorLog;
    }
  6. Q6. What are the governor limits for future methods?
    Ans: Key limits: - Concurrent jobs: 50 future calls per transaction - Jobs in org: 250,000 per 24 hours - Heap size: 12MB in async context - Execution time: 60 minutes - DML statements: 150 per transaction - SOQL queries: 200 per transaction - Callouts: 100 per transaction - Callout timeout: 10 seconds default - Callout response size: 6MB
  7. Q7. How do you monitor future method execution?
    Ans: Monitoring options: - Setup → Environments → Jobs → Apex Jobs - AsyncApexJob object queries - Custom logging within future methods - Platform Events for real-time notifications Example query:
    SELECT Id, Status, JobType, ApexClass.Name, CreatedDate, CompletedDate, NumberOfErrors
                  FROM AsyncApexJob
                  WHERE JobType = 'Future'
                  ORDER BY CreatedDate DESC
                  LIMIT 100
    Example custom logging:
    @future
    public static void processAccounts(Set accountIds) {
      Process_Log__c log = new Process_Log__c(
        Job_Type__c = 'Future',
        Start_Time__c = System.now(),
        Record_Count__c = accountIds.size()
      );
      insert log;
      
      try {
        // Process accounts
        // ...
        
        log.End_Time__c = System.now();
        log.Status__c = 'Completed';
        update log;
      } catch(Exception e) {
        log.End_Time__c = System.now();
        log.Status__c = 'Failed';
        log.Error_Message__c = e.getMessage();
        update log;
      }
    }
  8. Q8. How do you test future methods effectively?
    Ans: Testing strategies: - Test.startTest()/stopTest(): Forces job execution - Verify results: Check database state after execution - Error handling: Test failure scenarios - Callout mocking: For callout testing Example test:
    @isTest
    static void testFutureMethod() {
      // Create test data
      List testAccounts = new List();
      for(Integer i = 0; i < 10; i++) {
        testAccounts.add(new Account(Name = 'Test ' + i));
      }
      insert testAccounts;
      
      // Set up callout mock if needed
      Test.setMock(HttpCalloutMock.class, new MockHttpResponse());
      
      // Test future method execution
      Test.startTest();
      Set accountIds = new Set();
      for(Account acc : testAccounts) {
        accountIds.add(acc.Id);
      }
      AccountService.updateAccountStatus(accountIds);
      Test.stopTest();
      
      // Verify results
      List updatedAccounts = [SELECT Id, Status__c FROM Account WHERE Id IN :accountIds];
      for(Account acc : updatedAccounts) {
        System.assertEquals('Processed', acc.Status__c);
      }
      
      // Verify job completion
      List jobs = [SELECT Id, Status, NumberOfErrors FROM AsyncApexJob WHERE JobType = 'Future' ORDER BY CreatedDate DESC LIMIT 1];
      System.assertEquals(1, jobs.size());
      System.assertEquals('Completed', jobs[0].Status);
      System.assertEquals(0, jobs[0].NumberOfErrors);
    }
  9. Q9. How do you handle mixed DML operations with future methods?
    Ans: Mixed DML solution: - Problem: Cannot update user and setup objects in same transaction - Solution: Use future methods to separate operations Example:
    // Trigger handler - cannot update User and Account in same transaction
    public class UserAccountTriggerHandler {
      public static void handleUserUpdate(List users) {
        // Update user records
        update users;
        
        // Update related accounts in future method to avoid mixed DML
        Set accountIds = new Set();
        for(User u : users) {
          accountIds.add(u.AccountId);
        }
        
        if(!accountIds.isEmpty()) {
          updateAccountStatus(accountIds);
        }
      }
      
      @future
      public static void updateAccountStatus(Set accountIds) {
        List accounts = [SELECT Id, Status__c FROM Account WHERE Id IN :accountIds];
        for(Account acc : accounts) {
          acc.Status__c = 'User Updated';
        }
        update accounts;
      }
    }
  10. Q10. What are the best practices for future methods?
    Ans: Best practices: - Keep methods focused: Single responsibility principle - Handle errors gracefully: Don't let one record fail the job - Implement retry logic: For transient failures - Monitor execution: Log progress and errors - Test thoroughly: Cover success, failure, and edge cases - Secure parameters: Don't pass sensitive data unnecessarily - Consider limits: Stay within governor limits - Plan for failures: Implement proper error handling - Use meaningful names: For methods and jobs - Avoid infinite loops: In chaining logic - Validate input: In method parameters - Document limitations: For maintenance Example with best practices:
    public class FutureBestPractices {
      @future(callout=true)
      public static void processWithBestPractices(Set recordIds, String operationType) {
        // Validate input
        if(recordIds == null || recordIds.isEmpty()) {
          System.debug('No records to process');
          return;
        }
        
        // Log start
        Process_Log__c log = new Process_Log__c(
          Job_Type__c = 'Future',
          Start_Time__c = System.now(),
          Record_Count__c = recordIds.size(),
          Operation_Type__c = operationType
        );
        insert log;
        
        try {
          // Process based on operation type
          switch on operationType {
            when 'UPDATE_STATUS' {
              processStatusUpdate(recordIds);
            }
            when 'SEND_NOTIFICATION' {
              sendNotifications(recordIds);
            }
            when else {
              throw new IllegalArgumentException('Invalid operation type: ' + operationType);
            }
          }
          
          // Log success
          log.End_Time__c = System.now();
          log.Status__c = 'Completed';
          update log;
        } catch(Exception e) {
          // Log error
          log.End_Time__c = System.now();
          log.Status__c = 'Failed';
          log.Error_Message__c = e.getMessage();
          update log;
          
          // Send alert for critical failures
          if(isCriticalError(e)) {
            sendAlertEmail(e.getMessage());
          }
        }
      }
      
      private static void processStatusUpdate(Set recordIds) {
        List accounts = [SELECT Id, Status__c FROM Account WHERE Id IN :recordIds];
        for(Account acc : accounts) {
          acc.Status__c = 'Processed';
        }
        update accounts;
      }
      
      private static void sendNotifications(Set recordIds) {
        // Implementation for sending notifications
      }
      
      private static Boolean isCriticalError(Exception e) {
        // Determine if error is critical
        return e.getMessage().contains('critical') || e.getMessage().contains('permanent');
      }
      
      private static void sendAlertEmail(String message) {
        Messaging.SingleEmailMessage email = new Messaging.SingleEmailMessage();
        email.setToAddresses(new String[]{'admin@company.com'});
        email.setSubject('Future Method Critical Error');
        email.setPlainTextBody('A critical error occurred: ' + message);
        Messaging.sendEmail(new Messaging.SingleEmailMessage[]{email});
      }
    }
  11. Q11. How do you compare future methods to other async options?
    Ans: Comparison matrix:
    Feature @future Batch Apex Queueable
    Complex Parameters No (primitives only) Yes (with start method) Yes (constructor)
    Chaining No Yes (finish method) Yes (execute method)
    Monitoring Limited Yes Yes
    Error Handling Basic Advanced Advanced
    Use Case Simple async Large data volumes Flexible async
    When to use each: - @future: Simple fire-and-forget operations with primitive parameters - Batch Apex: Processing large data volumes (>10,000 records) - Queueable: Complex async processing with chaining and monitoring needs
  12. Q12. How do you abort a future method?
    Ans: Aborting methods: - Setup UI: Setup → Environments → Jobs → Apex Jobs → Abort - Apex:
    List jobs = [SELECT Id FROM AsyncApexJob WHERE JobType = 'Future' AND Status = 'Queued'];
    for(AsyncApexJob job : jobs) {
      System.abortJob(job.Id);
    }
    Note: Only queued jobs can be aborted, not running jobs. Future methods cannot be directly aborted once they start executing.
  13. Q13. How do you handle governor limits in future methods?
    Ans: Limit management techniques: - Check limits: Use Limits methods to monitor usage - Batch operations: Process in smaller chunks - Efficient querying: Use selective queries - Minimize DML: Combine operations - Use collections: For bulk processing Example:
    @future
    public static void processLargeDataSet(Set recordIds) {
      // Check current limits
      System.debug('DML statements used: ' + Limits.getDmlStatements() + '/' + Limits.getLimitDmlStatements());
      System.debug('SOQL queries used: ' + Limits.getQueries() + '/' + Limits.getLimitQueries());
      
      // Process in chunks to stay within limits
      List idList = new List(recordIds);
      Integer batchSize = 100;
      for(Integer i = 0; i < idList.size(); i += batchSize) {
        Integer endIndex = Math.min(i + batchSize, idList.size());
        List batch = idList.subList(i, endIndex);
        
        // Process this batch
        processBatch(batch);
      }
    }
    
    private static void processBatch(List batch) {
      List accounts = [SELECT Id, Name, Status__c FROM Account WHERE Id IN :batch];
      for(Account acc : accounts) {
        acc.Status__c = 'Processed';
      }
      update accounts;
    }
  14. Q14. How do you optimize future method performance?
    Ans: Performance optimization techniques: - Bulk processing: Process collections efficiently - Efficient queries: Use selective WHERE clauses - Minimize DML: Combine operations - Use maps/sets: For efficient lookups - Avoid SOQL/SOSL in loops: Move outside method - Batch large operations: Split into smaller chunks - Monitor heap usage: Stay under 12MB limit Example with optimization:
    @future
    public static void optimizedProcessing(Set accountIds) {
      // Efficient bulk processing
      Map accountMap = new Map(
        [SELECT Id, Name, Type, (SELECT Id, Email FROM Contacts) FROM Account WHERE Id IN :accountIds]
      );
      
      // Process accounts with their contacts
      List accountsToUpdate = new List();
      List contactsToUpdate = new List();
      
      for(Account acc : accountMap.values()) {
        // Update account
        acc.Last_Processed__c = System.now();
        accountsToUpdate.add(acc);
        
        // Update related contacts
        for(Contact c : acc.Contacts) {
          c.Processed__c = true;
          contactsToUpdate.add(c);
        }
      }
      
      // Bulk DML operations
      if(!accountsToUpdate.isEmpty()) {
        update accountsToUpdate;
      }
      
      if(!contactsToUpdate.isEmpty()) {
        update contactsToUpdate;
      }
    }
  15. Q15. How do you integrate future methods with external systems?
    Ans: Integration patterns: - Callouts in future methods: For external API calls - @future(callout=true): Required annotation - Error handling: For network failures - Retry mechanisms: For transient failures - Authentication: Secure token management Example:
    public class ExternalIntegration {
      @future(callout=true)
      public static void syncWithExternalSystem(Set accountIds, String authToken) {
        try {
          // Get account data
          List accounts = [SELECT Id, Name, Type, Industry FROM Account WHERE Id IN :accountIds];
          
          // Prepare data for external system
          List externalAccounts = new List();
          for(Account acc : accounts) {
            externalAccounts.add(new ExternalAccount(acc.Name, acc.Type, acc.Industry));
          }
          
          // Make callout
          Http http = new Http();
          HttpRequest request = new HttpRequest();
          request.setEndpoint('https://api.external.com/sync');
          request.setMethod('POST');
          request.setHeader('Authorization', 'Bearer ' + authToken);
          request.setHeader('Content-Type', 'application/json');
          request.setBody(JSON.serialize(externalAccounts));
          
          HttpResponse response = http.send(request);
          
          if(response.getStatusCode() == 200) {
            // Process success response
            processSuccessResponse(response.getBody(), accountIds);
          } else if(response.getStatusCode() == 429) {
            // Rate limited - log for manual retry
            logRateLimit(accountIds);
          } else {
            // Handle error
            handleError(response.getStatusCode(), response.getBody(), accountIds);
          }
        } catch(Exception e) {
          // Log network error
          logNetworkError(e.getMessage(), accountIds);
        }
      }
      
      private static void processSuccessResponse(String responseBody, Set accountIds) {
        // Process the successful response from external system
        // Update Salesforce records with external IDs or other data
        List accounts = [SELECT Id, External_Id__c FROM Account WHERE Id IN :accountIds];
        // Update with data from response
        update accounts;
      }
      
      private static void logRateLimit(Set accountIds) {
        Rate_Limit_Log__c log = new Rate_Limit_Log__c(
          Record_Ids__c = String.join(new List(accountIds), ','),
          Log_Time__c = System.now()
        );
        insert log;
      }
      
      private static void handleError(Integer statusCode, String responseBody, Set accountIds) {
        Error_Log__c errorLog = new Error_Log__c(
          Error_Type__c = 'External API Error',
          Status_Code__c = String.valueOf(statusCode),
          Message__c = responseBody,
          Record_Ids__c = String.join(new List(accountIds), ',')
        );
        insert errorLog;
      }
      
      private static void logNetworkError(String message, Set accountIds) {
        Error_Log__c errorLog = new Error_Log__c(
          Error_Type__c = 'Network Error',
          Message__c = message,
          Record_Ids__c = String.join(new List(accountIds), ',')
        );
        insert errorLog;
      }
    }

Back to Developer Home