6

We have private field service which is instance of HelperService. HelperService is pretty heavy class, that performs several callouts, transforms responses into Salesforce objects and logs errors which will be used for email messaging in the batch's finish method.

Based on query generation logic in the start method, batch either enters execute method and fails because of NullPoiner, or do not receive records for processing and proceeds to finish method where it fails with the same NullPoiner exception. service field appears to be null, even though we have instantiated it.

As seen on piece of code below service field is not static, and Database.Stateful interface has been implemented which means instance of HelperService should be serialized between chunks. This issue is not reproducible on sandbox, occur only in production. Also it fails only when ran via scheduler, manual launch with anonymous apex worked as expected, issue did not occur.

Have someone faced such behavior, if so - is there any possible workarounds? Thanks everyone in advance.

public class BatchClass implements Database.Batchable<SObject>, Database.Stateful {

    private final HelperService service = new HelperService();


    public Database.QueryLocator start(Database.BatchableContext bc) {
        //Start logic...
    }


    public void execute(Database.BatchableContext bc, List<SObject> records) {

        service.setBar(...) //Here NullPointer exception occurrs
    }


    public void finish(Database.BatchableContext bc) {

        service.getFoo() //Here NullPointer exception occurrs
    }
}
2
  • 2
    Have you tried to remove 'final' from service variable declaration?
    – wesaw
    Commented Jan 6, 2020 at 13:48
  • @wesaw final worked fine for all previous jobs. We`ll remove final with next deploy, but for now we are stuck with what we have. Commented Jan 6, 2020 at 14:01

4 Answers 4

3

In documentation we have an example with private final String initialState; variable. This batch class does not implement Database.Stateful. Maybe you should just remove final keyword because we implement this interface and mark this variable as public?

Also, investigate this article. It could be helpfull.

1
  • Thanks Pavlo. I've seen this article, it did not help that much. We'll remove final during next deployment. Commented Jan 6, 2020 at 14:10
2

There might be specific known issue in specific version of Salesforce API if issue occurs on Production but isn't seen in Sandbox.

For example, if your production has winter 19 release and sandbox has spring 20 release, the issue may exist in Winter 19 and may be fixed in spring 20.

First of all, open Salesforce support case to find out root cause and perform Root Cause Analysis. Provide information to Salesforce support about versions where issue is reproduced and where is not.

Also, please look through possible related knows issues (which may or may not be related to your actual case): Stateful batch job that stores Database.SaveResult (failed after validation errors) throws error during deserialization

Inconsistency of behaviour in BaseBatchApexRangeChunkHandler vs. BatchApexJobItemHandler, when implementing Database.Stateful

As a workaround, try

  1. remove final keyword
  2. move initialiser into batch constructor.
1
  • Thanks for suggestion. We'll remove final keyword, as well as adding additional HelperService instance fields, with different initializing methods. Commented Jan 6, 2020 at 14:47
0

I've tested this with the following code:

public class StatefulBatch implements Database.Batchable<sObject>, Database.Stateful
{
    private final StatefulBatchHelper helper = new StatefulBatchHelper();

    public List<SObject> start(Database.BatchableContext c)
    {
        System.debug(helper.a);
        System.debug(helper.b);
        return new List<SObject>{new Contact()};
    }

    public void execute(Database.BatchableContext c, List<sObject> scope)
    {
        System.debug(helper.a);
        System.debug(helper.b);    
    }

    public void finish(Database.BatchableContext c)
    {
        System.debug(helper.a);
        System.debug(helper.b);
    }
}

public class StatefulBatchHelper 
{
    public integer a = 5;
    public final string b = 'test';
}

After running it with both classes in API version 47, 46, and 43, I haven't been able to replicate the issue in the OP, the correct values are printed. I also tried doing a JSON serialization round trip and didn't have any issue. If removing final doesn't solve the issue, it might be worth trying to remove pieces from HelperService and testing to see if there is some member variable causing a serialization issue.

0

After a month of communication with Salesforce support we received following message:

Database.stateful is used to maintain the common handle and check the process but the Bean life cycle will not be maintained as per my analysis. Instead of instantiating the bean at class level. we recommend to instantiate the object in method level(i.e., execute, final start methods) and pass by reference wherever required in order to ensure the scope of the bean created.

Therefore using one service instance for whole batch class is not recommended.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .