[SalesForce] Does queueing a future method count as DML

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.

Best 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

Related Topic