function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
Sunay - KVP Bus SolnsSunay - KVP Bus Solns 

Calling future method from a trigger

Hi,

 

I am using a trigger to call the future method, but it gives me a error which says "Too many Future calls". There are 100 of records inserted, after which this method is called. Is there any way where I can overcome this error.

 

Please give me some inputs on this.

 

Thanks.

Best Answer chosen by Admin (Salesforce Developers) 
AdrianCCAdrianCC

Alright so here's my plan:

 

Instead of using a class that has a future method that can make just 10 requests for all your records, create a batch apex class that processes the record requests one at a time. The execute() method of the class will use the 10 calls just for 1 record, you can include a retry mechanism in case they time out. Since the batch is executed asynchronously you should be able to process a very large number of records like this.

 

1. create a batch apex class. See documentation here: http://www.salesforce.com/us/developer/docs/apexcode/Content/apex_batch_interface.htm

You'll need to create a start, execute and a finish method. 

2. the constructor of the batch should have a Set<String> argument so you can send the list of order numbers. Initialize a class variable with it so you can use it in the start() method when extracting the list of records

3. use Database.QueryLocator instead of Iterable when creating the start().

4. reuse your httprequest code from the future method above in the execute(). You'll only need to process 1 record. Put some sort of warning(email etc) when a callout fails...

5. in the trigger you'll call your class with smth like this:

Id batchInstanceId = Database.executeBatch(new batchClass(Set<String> orderNumbersSet), 1); 

 "1" means just 1 record from the list return by the start qerylocator is proccessed at a time by the execute().

 

Hope this helps and sorry for the late answer, got swamped with mail and work :) ...

 

Adrian

All Answers

Navatar_DbSupNavatar_DbSup

Hi,

 

You can write your future method in a class and then call that method in your trigger.

 

Did this answer your question? If not, let me know what didn't work, or if so, please mark it solved. 

Sunay - KVP Bus SolnsSunay - KVP Bus Solns

I have done that, since more than 100 records are inserting at a time, its giving me the error "Too Many future calls" as I understand that it has a limit of 10. And I need to overcome this, can you please suggest me a way

AdrianCCAdrianCC

There is a limit of a maximum of 10 asynchronous calls within the context of a single Apex logical transaction. You are hitting this limit because your trigger calls the method more than ten times.

 

Can you post your code so we can take a look? 

Why do you need the future calls?

 

Have a nice day,

Adrian

SeAlVaSeAlVa

Instead of calling the @future method 100 times, call it once and provida as parameters a list of the data you need.

 

(if you are providing 3 strings, change it to provide 3 List<String> and inside the  @future method, loop over you elements)

 

Regards

Sunay - KVP Bus SolnsSunay - KVP Bus Solns

I am inserting records through a webservice callout in a custom object, so on insertion of records, I would like to run a different class which will update the records with the data coming from a webservice callout. Basically on one call out records are inserted, and trigger is executed which will call the method, and upon execution of this method, there is one more http callout which will get other data that has to be updated in the records created earlier.

 

 

This is the trigger which is calling the method:

 

trigger listorderitems on Order_Details__c (after insert)
{

List<Order_Details__c> ordlist = new List<Order_Details__c>();
Set<Id> ordid = new Set<Id>();
for (Order_Details__c ord : trigger.new)
{
//ordlist.add(ord.Id);
ordid.add(ord.Id);
}

List<Order_Details__c> orderlist = [Select id,Order_Number__c from Order_Details__c where id = :ordid];

for(Order_Details__c order: orderlist)
{
ListOrderItems.Listorderitems(order.Order_Number__c);
}

}

 

This is the apex class through which the method is executing:

 

global class ListOrderItems{


@future(callout=true)
static webservice void Listorderitems(String orderid)
{

Order_Details__c ord = [Select id, Order_Number__c from Order_Details__c where Order_Number__c=:orderid];

// The date format is yyyy-MM-dd'T'HH:mm:ss.SSS'Z'
DateTime d = System.now();
String timestamp = d.format('yyyy-MM-dd')+'T'+ d.format('HH:mm:ss')+'Z';
System.debug(timestamp);
String urlEncodedTimestamp = EncodingUtil.urlEncode(timestamp, 'UTF-8');
String action = 'ListOrderItems';

//This date is used for the field CreatedAfter
DateTime dt = System.now() - 1;
System.debug(dt);
String dtime = dt.format('yyyy-MM-dd')+'T'+ dt.format('HH:mm:ss')+'.'+ dt.format('SSS')+'Z';
System.debug(dtime);
String urlEncodeddtime = EncodingUtil.urlEncode(dtime, 'UTF-8');

String AmazonOrderId = ord.Order_Number__c;
String SellerSkus = 'TJ-06NB-VVTF';
String urlToTest = 'amazon.com';
String version = '2011-01-01';
String SignatureVersion = '2';
String SignatureMethod = 'HmacSHA256';
String MarketplaceId= 'SAMPLEMARKETPLACEID';
String FulfillmentChannel = 'MFN';
String SellerId = 'SAMPLESELLERID';
String endpoint = 'https://mws.amazonservices.com/Orders/2011-01-01';
String accessKey = 'SAMPLEACCESSKEY';
String ResponseGroup = 'Detailed';
String QueryStartDateTime = urlEncodeddtime;


String inputstr = 'AWSAccessKeyId=' + accessKey +
'&Action=' + action +
'&AmazonOrderId=' + AmazonOrderId +
'&SellerId=' + SellerId +
'&SignatureMethod=' + SignatureMethod +
'&SignatureVersion=' + SignatureVersion +
'&Timestamp=' + urlEncodedTimestamp +
'&Version=' + version ;

System.debug(inputstr);

String StringtoSign = 'POST\n'+
'mws.amazonservices.com\n'+
'/Orders/2011-01-01\n' +
inputstr ;

//String StringtoSign = 'POST\nmws.amazonservices.com\n/Orders/2011-01-01\nAWSAccessKeyId=SAMPLEACCESSKEY&Action=ListOrderItems&AmazonOrderId=&SellerId=SAMPLESELLERID&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=2012-06-11T07%3A15%3A44Z&Version=2011-01-01';

System.debug(StringtoSign);

Blob mac = Crypto.generateMac('HMacSHA256', blob.valueof(StringtoSign),blob.valueof('SAMPLESECRETKEY'));

String macUrl = EncodingUtil.base64Encode(mac);

//String signed = make_sig(StringtoSign);

//System.debug(signed);

String codedsigned = EncodingUtil.urlEncode(macUrl,'UTF-8');

System.debug(codedsigned);

HttpRequest req = new HttpRequest();
//req.setEndpoint('https://mws.amazonservices.com/Orders/2011-01-01?AWSAccessKeyId=SAMPLEACCESSKEY&Action=ListOrderItems&SellerId=SAMPLESELLERID&SignatureVersion=2&Timestamp=2012-06-11T07%3A15%3A44Z&Version=2011-01-01&Signature=KAuyZ13Sll8vuG2%2BGMUKy%2F6%2BjoH4Fgl78kHyGj6XMuQ%3D&SignatureMethod=HmacSHA256&AmazonOrderId=');

req.setEndpoint(endpoint + '?AWSAccessKeyId=' + accessKey +
'&Action=' + action +
'&SellerId=' + SellerId +
'&SignatureVersion='+SignatureVersion +
'&Timestamp=' + urlEncodedTimestamp +
'&Version=' + version +
'&Signature=' + codedsigned +
'&SignatureMethod='+ SignatureMethod +
'&AmazonOrderId=' + AmazonOrderId);


req.setMethod('POST');
Http http = new Http();
try {
HttpResponse res = http.send(req);
System.debug('STATUS:'+res.getStatus());
System.debug('STATUS_CODE:'+res.getStatusCode());
System.debug('BODY: '+res.getBody());

String xml = res.getBody();

XmlStreamReader xsr = new XmlStreamReader(xml);

System.debug(xsr);


List<Order_Details__c> orderlist = new List<Order_Details__c>();

if(xsr.getEventType()==XmlTag.START_DOCUMENT)
{

xsr.next();

xsr.next();

while(xsr.getEventType()!=XmlTag.END_DOCUMENT)
{
xsr.next();

if(xsr.getLocalName() == 'OrderItem')
{
xsr.next();

//Order_Details__c ord = new Order_Details__c();

while(xsr.getLocalName() != 'OrderItem')
{
if(xsr.getLocalName() == 'SellerSKU')
{

xsr.next();
if (xsr.getEventType() == XmlTag.CHARACTERS)
{
ord.SKU__c = xsr.getText().trim();
xsr.next();
}
xsr.next();
continue;

}
if(xsr.getLocalName() == 'Title')
{

xsr.next();
if (xsr.getEventType() == XmlTag.CHARACTERS)
{
ord.Title__c = xsr.getText().trim();
xsr.next();
}
xsr.next();
continue;

}
if(xsr.getLocalName() == 'QuantityOrdered')
{

xsr.next();
if (xsr.getEventType() == XmlTag.CHARACTERS)
{
ord.Quantity_in_Order__c= Decimal.valueOf(xsr.getText().trim());
xsr.next();
}
xsr.next();
continue;

}

xsr.next();

}

orderlist.add(ord);

}
// xsr.next();
}
}


update orderlist;

System.debug(orderlist);
} catch(System.CalloutException e) {
System.debug('ERROR: '+ e);
}

}


}

 

 

 

AdrianCCAdrianCC

Alright.... @SeAlVa is right, one change would be to make your static webservice void Listorderitems take a List<String> instead of String. This will fix your "Too many Future calls" problem.

 

You will need to modify your httprequests code also. This is where I believe you'll run into more governor limits: 

Total number of callouts (HTTP requests or Web services calls) in a request	10 
Maximum timeout for all callouts (HTTP requests or Web services calls) in a request 	120 seconds

source: http://www.salesforce.com/us/developer/docs/apexcode/Content/apex_gov_limits.htm

 

I'll need to think a little more about this. A change of design might be needed. Donno why but I'm thinking batch apex might be better...

 

Thanks!

Adrian

Sunay - KVP Bus SolnsSunay - KVP Bus Solns

Hi if I take the List<string>, I am not able to call this method in the trigger. Is there anything which I need to check in the trigger?

Platy ITPlaty IT

I believe future methods won't accept lists, but will accept a set.  Try Set<String> instead.  But Adrian is right, you're going to hit an issue with the number of callouts in your future method, batch apex will be the only way to accomplish this.

Sunay - KVP Bus SolnsSunay - KVP Bus Solns

Ok I solved the issue of "Too Many Future Calls", but as Adrain mentioned now I am getting the error for too many callouts. And I am not sure how batch apex works, can you guys please help me on that.

AdrianCCAdrianCC

Alright so here's my plan:

 

Instead of using a class that has a future method that can make just 10 requests for all your records, create a batch apex class that processes the record requests one at a time. The execute() method of the class will use the 10 calls just for 1 record, you can include a retry mechanism in case they time out. Since the batch is executed asynchronously you should be able to process a very large number of records like this.

 

1. create a batch apex class. See documentation here: http://www.salesforce.com/us/developer/docs/apexcode/Content/apex_batch_interface.htm

You'll need to create a start, execute and a finish method. 

2. the constructor of the batch should have a Set<String> argument so you can send the list of order numbers. Initialize a class variable with it so you can use it in the start() method when extracting the list of records

3. use Database.QueryLocator instead of Iterable when creating the start().

4. reuse your httprequest code from the future method above in the execute(). You'll only need to process 1 record. Put some sort of warning(email etc) when a callout fails...

5. in the trigger you'll call your class with smth like this:

Id batchInstanceId = Database.executeBatch(new batchClass(Set<String> orderNumbersSet), 1); 

 "1" means just 1 record from the list return by the start qerylocator is proccessed at a time by the execute().

 

Hope this helps and sorry for the late answer, got swamped with mail and work :) ...

 

Adrian

This was selected as the best answer
sfdcFanBoysfdcFanBoy
Thank you very much AdrianCC. Your inputs are very useful. Given Kudos to your post.

so, if the callout fails, is there any way to retry ?

Can I Schedule another class every day to capture all the records that failed today and send callouts again ? is this a good option ?

What other options do we have ?

Cheers!
AdrianCCAdrianCC

Hello again!

 

Use a for loop to retry in case of fail. Something like:

HttpResponse res = new HttpResponse();
for (Integer i = 0; i < 10; i++) {
	res = http.send(req);
	if (res.getStatusCode() != 200) {//or whatever is the success code for your call
		break;
	}
}

 You'll need to save somewhere the ids of the records that failed if you want to send the callouts again. Implement stateful for you batch class and keep a list of them. Here's a thing that I'm not sure about: if you can start another batch from a batch class... If you can, then send the list of failed records to it and try to process them again...

You could also save the failed record ids to some helper object, like a custom setting. Your idea with the scheduler that tries to process them daily is pretty good. Be sure to delete them from the list after a certain amount of time, failed tries or successful ones

 

Happy Friday,

Adrian

 

sfdcFanBoysfdcFanBoy

Thanks Adrian.  The retry mechanism sounds good.  I will give it a try and let you know.

 

 

5. in the trigger you'll call your class with smth like this:

 

Id batchInstanceId = Database.executeBatch(new batchClass(Set<String> orderNumbersSet), 1); 

As mentioned by you in pointer 5, If we initiate a batch class from trigger, it hits the governor limits -  too many concurrent batch jobs in this org (limit is 5)! How do we take care of this ?  Please share your thoughts.

 

Cheers!

AdrianCCAdrianCC

Barring the fact that you have a bulk efficient trigger and the batch is started correctly with a set of ids, that means you are inserting multiple records at very close times(but different). So there are 5 or more trigger calls which in turn translate in 5 or more batches... :(

 

How are the Order_Details__c created? Through sfdc? API calls from another system? When did you get the error? Was it for a bulk insert with the data loader by any chance?

 

Thanks,

Adrian

sfdcFanBoysfdcFanBoy
Ok. Instead of calling it from the trigger directly. I have scheduled an apex class which picks up all the relevant records and makes call outs separately in batches. This helps in by passing all the errors.
tonantetonante

H Adrian:

 

 Wouldn't;t Manish still be limited by the number of callouts that he could make to Amazon's webservice?  I am using batch for GeoCode updates using Google Maps and it only allows 200 callouts per day.

 

 Thanks for any advice.

 

Tony