[SalesForce] Error: Future method cannot be called from a future or batch method

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);
    }
}

Best Answer

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

Related Topic