[SalesForce] API Callout (apex class) from workflow – You have uncommitted work pending,please commit before calling out

I want to make an api call to an external webservice, parse the result and create records with the results. I started by Creating a class to do those steps, called the apex in a workflow providing the inputs from the calling record. I then created a process to start the workflow when an organisation (Account) is created or updated. However, I get the exception

"Error Occurred: An Apex error occurred: System.CalloutException: You
have uncommitted work pending. Please commit or rollback before
calling out".

I believe this is because you cannot have a call out AND DML operations in the same transaction, so to narrow down the problem I altered the apex class to ONLY do the api call out (not even take inputs, and to return null – But I still get the same exception. 

I started by implementing Process.Plugin, but now I am using @InvocableMethod – Same issue with both. 

Example Apex class, part of workflow started by process –

global class invokeAreasCallout{
    @InvocableMethod(label='Areas Callout')
    //global static List<String> doAreaCallout(List<String> postcode) {
    global static List<String> doAreaCallout() {

        String postcode='B302PF';
        HttpResponse res;
        // Instantiate a new http object
        Http h = new Http();

        // Instantiate a new HTTP request, specify the method (GET) as well as the endpoint
        HttpRequest req = new HttpRequest();
        String requestURL='http://mapit.mysociety.org/postcode/';
        requestURL=requestURL+postcode;
        req.setEndpoint(requestURL);
        req.setMethod('GET');

        // Send the request, and return a response
        res = h.send(req);

       return null;
    }
}

 

Best Answer

You can't callout during the life cycle of a DML operation directly. This limitation was put in place to prevent excessive row lock times. You can callout and perform DML in the same transaction, provided you callout first. Usually, this means you need to perform your callout asynchronously:

global class invokeAreasCallout{
    @InvocableMethod(label='Areas Callout')
    //global static List<String> doAreaCallout(List<String> postcode) {
    global static List<String> doAreaCallout() {
        doAreaCalloutAsync();
        return null;
    }
    @future(callout=true) global static void doAreaCalloutAsync() {
        String postcode='B302PF';
        HttpResponse res;
        // Instantiate a new http object
        Http h = new Http();

        // Instantiate a new HTTP request, specify the method (GET) as well as the endpoint
        HttpRequest req = new HttpRequest();
        String requestURL='http://mapit.mysociety.org/postcode/';
        requestURL=requestURL+postcode;
        req.setEndpoint(requestURL);
        req.setMethod('GET');

        // Send the request, and return a response
        res = h.send(req);
    }
}

Note that this means you can't get the results from your callout during the initial record transaction, so you'll probably want to modify your invocable method to accept a list of record ID values, and perform a query in the future method (then callout, and finally commit any changes back to the database).

Related Topic