+ Start a Discussion
rebvijkumrebvijkum 

System.AsyncException: Future method cannot be called from a future or batch method: SendVFAsAttachment.sendVF(String, String, String, String, Id, String, String, String)

So my use case is, when a BatchApex runs for every one hour, it inserts records on QNews custom object.
After Trigger fire every time when a record is inserted on QNews object and  sends an email with a PDF attachment.
I’m getting following error when Batch Apex runs, FATAL_ERROR|System.AsyncException: Future method cannot be called from a future or batch method: SendVFAsAttachment.sendVF(String, String, String, String, Id, String, String, String).

My ApexBatch:
global with sharing class ArticleBatch implements Database.Batchable<string>{
public Iterable<string> start(Database.BatchableContext bc)
    return new ArticleBatchIterable();  }

public void execute(Database.BatchableContext bc, List<string> scope){
    string articleTypeName = scope[0];    
    QNewsUpdateBatch batchClassObj = new QNewsUpdateBatch();
    batchClassObj.insertQnewsData(articleTypeName);
}
global void finish(Database.BatchableContext bc){
      //code that sends an email for execution of Batch    
}

My Trigger:
trigger Send_PDF_to_SharePointhelp on QNews__c (after insert, after update) {
    for(QNews__c qn: Trigger.new){
        string filename = qn.Article_Title__c+'_'+qn.Article_Version_Number__c+'.pdf';
        String emailbody='This attachment is the PDF copy of Article:  '+qn.Article_Title__c+' with version number '+qn.Article_Version_Number__c;   SendVFAsAttachment.sendVF('vijaku@vsp.com',qn.Data_Category__c,emailbody,UserInfo.getSessionId(),qn.Article_ID__c,qn.Article_Title__c,filename,qn.Article_Type__c); 
    }  
}

Future Method:
public class SendVFAsAttachment{
    @future(callout=true)
    public static void sendVF(String EmailIdCSV, String Subject,String body,String userSessionId, ID articleid,String ArticleTitle,String attachmentfilename,String ArticleType)
    {
        string newID = String.valueOf(articleid);
        String addr = 'https://vsp--kmbuild.cs10.my.salesforce.com/services/apexrest/sendPDFEmail';
        HttpRequest req = new HttpRequest();
        req.setEndpoint( addr );
        req.setMethod('POST');
        req.setHeader('Authorization', 'OAuth ' + userSessionId);
        req.setHeader('Content-Type','application/json');
        Map<String,String> postBody = new Map<String,String>();
        postBody.put('EmailIdCSV',EmailIdCSV);
        postBody.put('Subject',Subject);
        postBody.put('body',body);
        postBody.put('newID',newID);
        postBody.put('attachmentfilename',attachmentfilename);
        postBody.put('ArticleTitle',ArticleTitle);
        postBody.put('ArticleType',ArticleType);
        String reqBody = JSON.serialize(postBody);
        req.setBody(reqBody);
        Http http = new Http();
        HttpResponse response = http.send(req);
    }
}

Class to send email, exposed to REST API:
@RestResource(urlMapping='/sendPDFEmail/*')
Global class GETPDFContent{
     @HttpPost
    global static void sendEmail(String EmailIdCSV, String Subject, String body,string newID,string attachmentfilename,string ArticleTitle,string ArticleType) {
    List<String> EmailIds = EmailIdCSV.split(',');
        PageReference ref = Page.Multi_Topic_PDF;
        if(ArticleType=='Multi_Topic__kav'){
            ref = Page.Multi_Topic_PDF; 
          }
         if(ArticleType=='Additional_Benefits__kav'){
            ref = Page.AdditionalBenefits_PDF; 
          }
         if(ArticleType=='Core_Benefit__kav'){
            ref = Page.CoreBenefit_PDF; 
          }
         if(ArticleType=='Multi_Topic_Details__kav'){
            ref = Page.MultiTopicDetails_PDF; 
          }
         if(ArticleType=='Open_Enrollment__kav'){
            ref = Page.OpenEnrollment_PDF; 
          }
         if(ArticleType=='Single_Topic__kav'){
            ref = Page.SingleTopic_PDF; 
          }
            ref.getParameters().put('id',newID); 
            ref.setRedirect(true);
        Blob b = ref.getContentAsPDF();
        Messaging.SingleEmailMessage email = new Messaging.SingleEmailMessage();
        Messaging.EmailFileAttachment efa1 = new Messaging.EmailFileAttachment();
        efa1.setFileName(attachmentfilename);
        efa1.setBody(b);
        String addresses;
        email.setSubject( Subject );
        email.setToAddresses(EmailIds);
        email.setPlainTextBody(Body);
        email.setFileAttachments(new Messaging.EmailFileAttachment[] {efa1});
        Messaging.SendEmailResult [] r = Messaging.sendEmail(new Messaging.SingleEmailMessage[] {email});
    }
}
BalajiRanganathanBalajiRanganathan
The batch execution, the insert into QNews__c and the trigger Send_PDF_to_SharePointhelp will be in single transaction. so it means you are calling future method sendVF from the batch execution which is not allowed and thus the error you are getting.

The solution/workaround I can think now is,
1) Refactor/Overload your  public static void sendVF method to have two versions, one using future and on with out future.
2) Add a new flag to your QNews__c object
3) On the Trigger call SendVFAsAttachment.sendVF only if the flag is false
4) On the batch code make the new flag as True while inserting into QNews
5) On the batch, call the method wich is defined with out future

 
Simone CapelliSimone Capelli
Hi,
the Future methods cannot be called from Batch processes, you could check in the trigger if insert/update is triggered by Future, Batch or not 

for(QNews__c qn: Trigger.new){

   if (System.isFuture() || System.isBatch())
        sendVF(............);
    else
        sendVFNoFuture(............);

}

 @future(callout=true)
 public static void sendVF(String EmailIdCSV, String Subject,String body,String userSessionId, ID articleid,String ArticleTitle,String attachmentfilename,String ArticleType)
{
    sendVFNoFuture(............);
}

public void sendVFNoFuture(.............)
{
   string newID = String.valueOf(articleid);
        String addr = 'https://vsp--kmbuild.cs10.my.salesforce.com/services/apexrest/sendPDFEmail';
        HttpRequest req = new HttpRequest();
        req.setEndpoint( addr );
        req.setMethod('POST');
        req.setHeader('Authorization', 'OAuth ' + userSessionId);
        req.setHeader('Content-Type','application/json');
        Map<String,String> postBody = new Map<String,String>();
        postBody.put('EmailIdCSV',EmailIdCSV);
        postBody.put('Subject',Subject);
        postBody.put('body',body);
        postBody.put('newID',newID);
        postBody.put('attachmentfilename',attachmentfilename);
        postBody.put('ArticleTitle',ArticleTitle);
        postBody.put('ArticleType',ArticleType);
        String reqBody = JSON.serialize(postBody);
        req.setBody(reqBody);
        Http http = new Http();
        HttpResponse response = http.send(req);
}
BalajiRanganathanBalajiRanganathan

it is good point, we can achive using System.isFuture() || System.isBatch() with out using a flag.

but  Since Trigger does not allow call outs the sendVFNoFuture method should be called from Batch Process and not from the Trigger.

The Trigger should have
if (!System.isFuture() && !System.isBatch())
     sendVF(............);       
}
rebvijkumrebvijkum
what limitations i will be facing if i use

My Trigger:
trigger Send_PDF_to_SharePointhelp on QNews__c (after insert, after update) {
    for(QNews__c qn: Trigger.new){
        string filename = qn.Article_Title__c+'_'+qn.Article_Version_Number__c+'.pdf';
        String emailbody='This attachment is the PDF copy of Article:  '+qn.Article_Title__c+' with version number '+qn.Article_Version_Number__c; 

if (!System.isFuture() && !System.isBatch()){
 SendVFAsAttachment.sendVF('vijaku@xxx.com',qn.Data_Category__c,emailbody,UserInfo.getSessionId(),qn.Article_ID__c,qn.Article_Title__c,filename,qn.Article_Type__c); 
}

    }  
}
BalajiRanganathanBalajiRanganathan

I dont see other limitations except that when you write any other batch process or future method you need to remember to call the sendVF no future version and it is little more maintanence.
rebvijkumrebvijkum
i need to use future method. So i can't use above code. Is there any other way???