We need to implement the following pattern at my org:
- callout to external data source
- if that callout takes too long (according to some configurable threshold), log an error (ie do some DML)
- if that callout timed out on the remote server, try it again
Recognizing the potential for the dreaded “You have uncommitted work pending. Please commit or rollback before calling out.” error, I put the error logging code in a future method, thus isolating the DML from the callouts. However, the error is still being thrown. I reduced the issue down to this pattern:
public static void foo() { Http http = new Http(); HttpRequest req = new Httprequest(); req.setEndpoint('https://test.salesforce.com'); //whatever endpoint req.setMethod('GET'); http.send(req); //works fine bar(); http.send(req); //throws calloutexception } @future public static void bar() { }
Am I correct to assume that calling a future method counts as a DML operation? Is there any documentation I’m missing somewhere?
Here is the log for the above code snippet:
Error on line 8, column 1: System.CalloutException: You have uncommitted work pending. Please commit or rollback before calling out AnonymousBlock: line 8, column 1 41.0 APEX_CODE,DEBUG;APEX_PROFILING,INFO;CALLOUT,INFO;DB,INFO;SYSTEM,DEBUG;VALIDATION,INFO;VISUALFORCE,INFO;WAVE,INFO;WORKFLOW,INFO 12:41:06.606 (606820392)|UNKNOWN|[EXTERNAL] 12:41:06.606 (606893134)|EXECUTION_STARTED 12:41:06.606 (606904404)|CODE_UNIT_STARTED|[EXTERNAL]|execute_anonymous_apex 12:41:06.606 (608097326)|SYSTEM_MODE_ENTER|false 12:41:06.606 (608671843)|CALLOUT_REQUEST|[6]|System.HttpRequest[Endpoint=https://test.salesforce.com, Method=GET] 12:41:06.606 (737558005)|CALLOUT_RESPONSE|[6]|System.HttpResponse[Status=OK, StatusCode=200] 12:41:06.606 (862244488)|EXCEPTION_THROWN|[8]|System.CalloutException: You have uncommitted work pending. Please commit or rollback before calling out 12:41:06.606 (862467817)|SYSTEM_MODE_EXIT|false 12:41:06.606 (862668955)|FATAL_ERROR|System.CalloutException: You have uncommitted work pending. Please commit or rollback before calling out AnonymousBlock: line 8, column 1 12:41:06.862 (862819672)|CUMULATIVE_LIMIT_USAGE 12:41:06.862 (862819672)|LIMIT_USAGE_FOR_NS|(default) Number of SOQL queries: 0 out of 100 Number of query rows: 0 out of 50000 Number of SOSL queries: 0 out of 20 Number of DML statements: 0 out of 150 Number of DML rows: 0 out of 10000 Maximum CPU time: 0 out of 10000 Maximum heap size: 0 out of 6000000 Number of callouts: 1 out of 100 Number of Email Invocations: 0 out of 10 Number of future calls: 1 out of 50 Number of queueable jobs added to the queue: 0 out of 50 Number of Mobile Apex push calls: 0 out of 10 12:41:06.862 (862819672)|CUMULATIVE_LIMIT_USAGE_END 12:41:06.606 (862919688)|CODE_UNIT_FINISHED|execute_anonymous_apex 12:41:06.606 (864947070)|EXECUTION_FINISHED
I have also posted this question on the developer forums.
Answer
Using System.enqueueJob, Database.executeBatch, System.scheduleBatch, System.enqueueJob, and @future methods all modify the state of the database, so for purposes of callouts, count as a DML operation. This also means that Database.rollback can undo a scheduled job, batch job, queueable, or future method. Also note that any of those methods will prevent you from using PageReference.getContent, which counts as a callout, as well as web service methods from imported WSDL classes.
Example
public class q203304 {
@future public static void x() {
}
}
Execute Anonymous
q203304.x();
Blob c = new PageReference('https://www.google.com/').getContent();
Output:
Line: 2, Column: 1
System.CalloutException: You have uncommitted work pending. Please commit or rollback before calling out
Attribution
Source : Link , Question Author : Jim Madrigal , Answer Author : sfdcfox