1

I have a managed packaged that I recently updated and it threw an error for a future method I have that sends information to a third party. The error I received was:

Future method cannot be called from a future or batch method.

Since I cannot see any of the classes in the package I am assuming the method that triggered mine is a future/batch method.

Knowing that, I decided to put in a check:

If(System.future()){
    //do non-future method
else{
   //future method

However in other posts I read that I should just remove "@future"since we can not call a future from a future so that is what my non-future method is. When I did and included the check above, it threw this error:

Callout from triggers are currently not supported.

My future method(My non-future method is basically the same but without the @future annotation:

 @future(callout = true)
public static void updateOrderStatus(Set<Id> orderIds, String errorMessage){ 
    List<OrderStatus> ordStatus = new List<OrderStatus>();
    Map<Id, Id> orderToDistributionMap = new Map<Id, Id>();
    List<Package__c> relevantPacks = [SELECT ID, Order__c, distribution__c FROM package__c WHERE Order__c IN :orderIds];
    for(Package__c pack : relevantPacks){
        if(orderToDistributionMap.get(pack.Order__c) == null){
            orderToDistributionMap.put(pack.Order__c, pack.Distribution__c);
        }
    }

    List<Order> orders = [SELECT ID, Status__c FROM Order WHERE ID IN :orderIds];
    for(order ord : orders){
        ordStatus.add(new OrderStatus(ord, orderToDistributionMap.get(ord.id)));
    }

    HttpRequest req = new HttpRequest();
    String env = ERFUtils.getEnvironment();
    String endpoint =  Link__c.getValues(env).ERFapi__c+'Packages/status';
    String token = Link__c.getValues(env).Token__c; 

    req.setEndpoint(endpoint);
    req.setHeader('x-ERF-auth', token);
    req.setHeader('Content-Type', 'application/json');
    req.setMethod('POST');

    Http http = new Http();
    HttpResponse res = new HttpResponse();
    if(!Test.isRunningTest()){
        try {
            for(OrderStatus status : ordStatus){
                req.setBody(JSON.serialize(status));
                res = http.send(req);
            }
        } catch(System.CalloutException e) {
            Tasks.statusFailTask(e.getMessage() + ' ERROR:'+orderIds + ' '+e.getStackTraceString() +errorMessage);
        }
    }
}

I also then read I should try to convert my method to "queueable". I Have not tried it yet and was wondering if I'm just missing something or is that the approach I should go for. I would like to send my information to the 3rd party in real time after the managed package's method is triggered. So I guess how can I implement a future method to then be followed by another future method? Or what are my other options?

Edit:

public class updateStatusOnOrders implements Queueable {

    public Set<Id> orderIds;
    public String errorMessage;

    public updateStatusOnOrders(Set<Id> orderIds, String errorMessage){
        this.shipmentIds = orderIds;
        this.errorMessage = errorMessage;
    }
    public void execute(QueueableContext context) {

      EDFService.updateOrderStatus(orderIds, errorMessage);
    }
}
  • others will jump in with direct answers but your situation is why I like the Dan Appleman Advanced Apex 4th edition asynchronous pattern , chapter "Going Asynchronous" as it avoids all these issues and provides a replay mechanism in case the callout fails – cropredy Dec 19 '18 at 2:06
2

First thing even though future methods are there for a while they are a thing of past. Going forward unless you have existing future code the suggestion is to move your code to Queueable apex. Think of Queueable apex as Future 2.0

Queueable or Future or Batch why do you need them to make a callout from trigger?

Because a callout has to be made asynchronously from apex trigger because callouts can hold your database transactions until they're completed. This could obviously cause significant contention with other transactions and impact performance. Now by making a asynchronous call you're running a separate independent transaction other than your current transaction and your dml's doesn't have to wait for your callout response. Read more about HTTP Callout from Triggers

Future method: The issue with future methods is that you cannot chain them.

Queueable: Queueable are much more flexible in terms of functionality compared to future methods. They can be chained like batch apex. You can also call a batch class or a future method from them.

Batch Apex: We typically prefer batch apex when we are dealing with huge data or trying to leverage relaxation of some of the governor limits like the number of records that can be queried or chunking the data based on scope and executing them in smaller batcher which acts as independent transaction, Even though these are much more powerful they needs to be used with much consideration. Read more about Is it advisable to call Batch from trigger

What exactly is Ideal for your case?

Queueable seems to be ideal in your situation. You can still prefer to use your if else logic but you else condition should be calling a Queueable instead or all together just call a Queueable class. What you will be getting doing this(Calling Queueable) is you bypass the callouts using asynchronous thread as well as you don't have to worry if it's a future or Queuable or even Batchable on the other end

Now there are lot more things I didn't cover in this answer but as far as you're error is concerned this should give you a decent understanding and nice head start.

Edit: Make sure to Implement Database.AllowsCallouts If you are trying to make callout from Queueable Context

  • Hi thank you for your feedback. I have edited my post to include my queue able class now. Can I keep my method in the future method or should I remove that annotation completely now since I have the queue able class now? – J.koh Dec 19 '18 at 17:17
  • @J.koh There is no need for additional @future annotation and Queueable should just suffice your need! Instead of your If(System.future()){//do non-future methodelse{ you can just call the Queueable directly! – codeyinthecloud Dec 19 '18 at 17:19
  • I called the queueable directly and then i received this error: Callout not allowed from this future method. Please enable callout by annotating the future method. eg: @Future(callout=true) – J.koh Dec 19 '18 at 18:24
  • Oh yeah your queueable also needs to implement Database.AllowsCallouts like this public class updateStatusOnOrders implements Queueable, Database.AllowsCallouts{} – codeyinthecloud Dec 19 '18 at 18:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.