0

I can't figure out what I'm doing wrong here. So, I have a batch class, when I call it, it instantiates a new List<Database.SaveResult>() which is non-static and since I'm implementing Database.Stateful I thought that that's all I had to do, but when it gets referenced in the finish() method, it's null!!

My error:

NullPointerException: Attempt to de-reference a null object
BatchablePlacementChecker.finish() line 27

The line that is failing is this (second line of the finish() method):

 if (errors.size() < 1 && !Test.isRunningTest()) {return;}

'errors' is the name of my variable, new List<Database.SaveResult>(); which gets instantiated in the constructor when I call the batch class.

The execute() method adds any Database.SaveResult objects to the errors variable if the save result isn't a success. I am collecting them to email them as a CSV to any currently active System Administrators in the finish() method, just so you have the background.

I'm calling this batch class in my test like this:

Database.executeBatch(new BatchablePlacementChecker(), 50);

Here's the full class:

public without sharing class BatchablePlacementChecker implements Database.Batchable<SObject>, Database.Stateful {
    public List<Database.SaveResult> errors;

    public BatchablePlacementChecker() {
        List<Database.SaveResult> errors = new List<Database.SaveResult>();
    }

    public Database.QueryLocator start(Database.BatchableContext dbc) {
        return Database.getQueryLocator('SELECT Id, On_Placement__c, (SELECT Id FROM Placements__r WHERE (Start_Date__c <= TODAY AND End_Date__c > TODAY) OR Confirmed_Start_Date__c <= TODAY) FROM Candidate__c');
    }

    public void execute(Database.BatchableContext dbc, List<Candidate__c> candidates) {
        List<Candidate__c> processed = new List<Candidate__c>();
        for (Candidate__c candidate : candidates) {
            candidate.On_Placement__c = candidate.Placements__r.size() > 0 ? true : false;
            processed.add(candidate);
        }
        List<Database.SaveResult> results = Database.update(processed, false);
        for (Database.SaveResult result : results) {
            if (!result.isSuccess()) {errors.add(result);}
        }
    }

    public void finish(Database.BatchableContext dbc) {
        sendNotification('Batch Job Completed', 'The BatchablePlacementChecker job has finished.');
        if (errors.size() < 1 && !Test.isRunningTest()) {return;}
        String csvOfIds = 'ID,Errors\n';
        for (Database.SaveResult erroredRecord : errors) {
            csvOfIds += erroredRecord.id + ',' + erroredRecord.errors + '\n';
        }
        Messaging.SingleEmailMessage email = new Messaging.SingleEmailMessage();
        Messaging.EmailFileAttachment attachment = new Messaging.EmailFileAttachment();
        attachment.setFileName('[' + Date.today() + '] BatachablePlacementCheckerErrors.csv');
        attachment.setBody(Blob.valueOf(csvOfIds));
        email.setFileAttachments(new Messaging.EmailFileAttachment[]{attachment});
        String[] toAddresses = new String[]{};
        for (User administrator : [SELECT Id, Email FROM User WHERE Profile.Name = 'System Administrator' AND IsActive = TRUE]) {
            toAddresses.add(administrator.Email);
        }
        email.setToAddresses(new String[]{'[email protected]'}); //Update this to toAddresses for live.
        email.setSubject('Errors During BatchablePlacementChecker'); 
        email.setHtmlBody('Some Candidate records failed to save when running the BatachablePlacementChecker class.');
        if (!Test.isRunningTest()) {Messaging.sendEmail(new Messaging.SingleEmailMessage[] {email});}
    }

    private void sendNotification(String title, String body) {
        CustomNotificationType notificationType = [SELECT Id, DeveloperName FROM CustomNotificationType WHERE DeveloperName = 'Batch_Job'];
        Messaging.CustomNotification notification = new Messaging.CustomNotification();
        notification.setTitle(title);
        notification.setBody(body);
        notification.setNotificationTypeId(notificationType.Id);
        notification.setTargetId(UserInfo.getUserId());
        try {notification.send(new Set<String>{UserInfo.getUserId()});}
        catch (Exception e) {System.debug('Problem sending notification: ' + e.getMessage());}
    }
}

2 Answers 2

2

The problem is the datatype declared in the constructor.

public List<Database.SaveResult> errors;

public BatchablePlacementChecker() {
    List<Database.SaveResult> errors = new List<Database.SaveResult>();
}

This is what we refer to as Name Shadowing. Basically, you created a local variable that overrode the instance variable by the same name. This isn't unique to constructors; you can do so in any method (constructors are just a special type of method).

4
  • 1
    Oh my god, it's the man himself! Thank you fox, you've carried me through my job and you're here to save me again lol! Also, wow, I didn't even know that was possible - thanks so much for making me aware as that is completely new to me, so it's time to fill this knowledge gap. Thank you so much for your help, here and pretty much every thread I read!
    – User
    Commented Jul 1, 2022 at 12:28
  • 2
    @NicoVarley No problem, glad I was able to help fill a lacuna.
    – sfdcfox
    Commented Jul 1, 2022 at 12:33
  • Well I learnt a new word "Lacuna"
    – Elijah
    Commented Jul 1, 2022 at 17:20
  • I JUST REALISED! It's supposed to be this.errors what a blunder!!
    – User
    Commented Jul 1, 2022 at 19:05
0

It's because I was instantiating the variable with the constructor.

Changing this...

public without sharing class BatchablePlacementChecker implements Database.Batchable<SObject>, Database.Stateful {
    public List<Database.SaveResult> errors;

    public BatchablePlacementChecker() {
        List<Database.SaveResult> errors = new List<Database.SaveResult>();
    }

To this...

public without sharing class BatchablePlacementChecker implements Database.Batchable<SObject>, Database.Stateful {
    public List<Database.SaveResult> errors = new List<Database.SaveResult>();

    public BatchablePlacementChecker() {}

...Solved the issue.

I'm not sure why though, I think I need to go research how constructors work properly!

You must log in to answer this question.

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