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
- 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 - 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); - 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
Example of invalid parameters:accountIds, List statusValues, Map config) { // Valid parameters List accounts = [SELECT Id, Name FROM Account WHERE Id IN :accountIds]; // Process accounts with statusValues and config } @future public static void processAccounts(List
accounts) { // This will cause a compile error // SObjects cannot be passed to future methods } - 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()); } } - 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; } - 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 - 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; } } - 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); } - 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; } } - 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}); } } - 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 - Q12. How do you abort a future method?
Ans: Aborting methods: - Setup UI: Setup → Environments → Jobs → Apex Jobs → Abort - Apex:List
Note: Only queued jobs can be aborted, not running jobs. Future methods cannot be directly aborted once they start executing.jobs = [SELECT Id FROM AsyncApexJob WHERE JobType = 'Future' AND Status = 'Queued']; for(AsyncApexJob job : jobs) { System.abortJob(job.Id); } - 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; } - 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; } } - 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; } }