[SalesForce] Rest callout via Batch Apex throws invalid session Id error

My Question is exactly same as this one – https://developer.salesforce.com/forums/?id=9060G0000005MLWQA2

But i will give more details.

We just turned on Lightning Knowledge on in our environment, and goal is to write a script that can update recordtype on all Draft and published articles.

For published articles, i need to do this –

Step 1 - Make a rest call to create a draft version  
Step 2 - Make a rest call to update draft version (change its recordtype) 
Step 3 - Make a rest call to publish the above draft version

For Articles that are in draft, i can reuse the code for Step 2 (even though updating draft can be done by simple database changes via apex, but that causes issue in conjunction with Step 3 if everything is done in one transaction .. so i am sticking to rest call for step 2).

Since there are over 4000 articles, and each could require upto 3 rest calls, i am writing a batch apex, and intend to run it with a batch size of 1.

For reference, i am using this to make rest calls – https://www.jitendrazaa.com/blog/salesforce/call-salesforce-rest-api-from-apex/

I wrote code for step 2, and tried testing it, but getting the error {"errorCode" : "INVALID_SESSION_ID", "message" : "Session expired or invalid"}

Instead of batch class, if i call my utility method directly from workbench or anonymous window, it works (for a single article).

here are my codes

batch class

global class pf_knowledgeMigrator implements Database.Batchable<sObject>, Database.AllowsCallouts{

    global Database.QueryLocator start(Database.BatchableContext BC){
    String query = 'Select Id, PublishStatus from Knowledge__kav where PublishStatus=\'Draft\' and IsLatestVersion= True and RecordTypeId=null';
        return Database.getQueryLocator(query);
    }


    global void execute(Database.BatchableContext BC, List<Knowledge__kav> articles){
        for(Knowledge__kav currentArticle : articles){
                knowledgeMigratorHelper.processDraftArticle(currentArticle);
        }
    }

    global void finish(Database.BatchableContext BC) {

    }
}

My helper class

public without sharing class knowledgeMigratorHelper{

    private static Id howToRecTypeId = [Select Id from RecordType where DeveloperName = 'How_To' limit 1].Id;

    public static void processDraftArticle(Knowledge__kav article){

        String articleId = String.ValueOf(article.Id);
        String howToRecordTypeIdString = String.ValueOf(howToRecTypeId);
        HttpResponse httpResponse = changeRecordType(articleId,howToRecordTypeIdString);
        if(httpResponse.getStatusCode() != 204){
            String response = JSON.serializePretty( JSON.deserializeUntyped(httpResponse.getBody()) );
            System.debug(response);
        }
    }

    /*
    * Utility Method to change recordType of a draft version of an article
    */
    public static HttpResponse changeRecordType(String articleId, String recTypeId){
        String sfdcURL = URL.getSalesforceBaseUrl().toExternalForm();
        String restAPIURL = sfdcURL + '/services/data/v40.0/sobjects/Knowledge__kav/'+articleId+'?_HttpMethod=PATCH';
        HttpRequest httpRequest = new HttpRequest(); 
        httpRequest.setMethod('POST');
        httpRequest.setHeader('Authorization', 'OAuth ' + UserInfo.getSessionId()); 
        httpRequest.setHeader('Authorization', 'Bearer ' + UserInfo.getSessionID());
        httpRequest.setHeader('Content-Type', 'application/json');
        httpRequest.setEndpoint(restAPIURL);
        httpRequest.setBody('{ "RecordTypeId": "' + recTypeId + '"}'); 
        Http http = new Http();
        HttpResponse httpResponse = http.send(httpRequest);
        return httpResponse;
    }
} 

When i run the command Database.executeBatch(new pf_knowledgeMigrator(),1), i get that error

However, if i directly use console anonymous window or workbench and test my utility method like following, it works –

String articleId = 'ka1c00000000YYvAAM';
String recTypeId = '012c0000000AWxvAAG';

HttpResponse httpResponse = knowledgeMigratorHelper.changeRecordType(articleId,recTypeId);
System.debug('@@@@ response code'+httpResponse.getStatusCode());

Doing above will udpate the recordtype of article with Id ka1c00000000YYvAAM

So something is different when REST callout is made via batch. Not sure how i can make this work

Best Answer

The session id that you get back from UserInfo.getSessionId() is not valid from batch code. This is actually due to be changed in the Winter 19 release:

API calls sometimes require a session ID and a URL. You can obtain that session ID using the System.UserInfo.getSessionId() method. This method previously returned null in asynchronous Apex, but it now returns a value whether it’s run synchronously or asynchronously.

https://releasenotes.docs.salesforce.com/en-us/winter19/release-notes/rn_apex_streamline_api_calls.htm

Edit

In comments, I suggested that the questioner tries making the batch class stateful, then gets the session id before launching the batch. They report that this works. Probably not 100% bulletproof as the session may expire in a long batch, but seems to be a stop-gap solution until Winter 19.