Monday, September 6, 2010

Batch Apex & First error: Attempt to de-reference a null object !

I stumbled upon a strange Batch Apex issue today. My batch job was going well in few SFDC orgs; after deploying the same to a new org it stopped working completely. I just see, “First error: Attempt to de-reference a null object” in debug and apex logs.  After adding a lot of debug statements in both start() and execute() method, I found that “execute(Database.BatchableContext BC, List<Sobject> scope)” is never called after creating QueryLocator in start().

So, finally after searching on dev forums found this solution; it says don’t query NULL columns/fields in initial SOQL i.e. the soql string given to QueryLocator in start(). This really fixed my problem.

So to put it all together in a clean way. We usually have batch jobs written in following manner

global class MyCoolBatch implements Database.Batchable<Sobject>{
     
  global Database.QueryLocator start(Database.BatchableContext BC){
    String query = 'Select X, Y, Z From Contact WHERE ...';
    Database.Querylocator qr = Database.getQueryLocator(query);
    return qr;
  }
  
  global void execute(Database.BatchableContext BC, List<Sobject> scope){
    // process the records in scope, as per biz logic
  }
  
  global void finish(Database.BatchableContext BC){}
}

Here we are querying fields X, Y and Z from Contact; in case any of X,Y or Z are NULL in any records, you will face the “First error: Attempt to de-reference a null object” issue. Solutions to this problem are

  1. Add null checks in SOQL, for fields that could go NULL for ex. X != null. This might not be possible, as this will for sure change the records returned from the SOQL.
  2. Don’t query the fields that could possibly be NULL in start() method, re-query those in “execute(Database.BatchableContext BC, List<Sobject> scope)” method. This should be safer, as execute() will always be working on smaller subset of records for ex. 200 records.

I used the later option i.e. don’t query NULLABLE fields in start() & re-query those fields in execute() method, this fixed the problem !

5 comments:

&gt;&gt;&gt;@.D.i.L said...

Hi My requirement is to update Parent Object field ( i.e opportunity field) based on change in child records.

hereis my code so far kindly assist
my id is adiludm@gmail.com
--------------------------------------
global class updateOpportunityStage implements Database.Batchable,Schedulable{
global string query ;

global updateOpportunityStage(){

Query = 'Select Name,Id from BigMachines__Quote__c' ;

}

global database.querylocator start(Database.BatchableContext BC){
return Database.getQueryLocator(query);
}
global void execute(SchedulableContext SC){
updateOpportunityStage stg = new updateOpportunityStage();
database.executebatch(stg);

}

global void execute(Database.BatchableContext BC, List scope){


// List oppList = new List() ;
for(sObject s : scope){

BigMachines__Quote__c quote = (BigMachines__Quote__c)s;
// System.debug('Adil'+quote);

List liOppIds;

for (BigMachines__Quote__c bmq :[ SELECT id,BigMachines__Status__c from BigMachines__Quote__c where BigMachines__Status__c = '*unison*' and BigMachines__Is_Primary__c = true ])
{

//collect opportunity Ids in a list
liOppIds.add(bmq.BigMachines__Opportunity__c);


}
//query all the opportunities in a single query

List opp = [select id, StageName from Opportunity where id in :liOppIds];
for ( Opportunity opps : opp)
{
opps.StageName = 'Closed Won' ;
}
//update all opportunities in a single DML

update opp;

}

}
global void finish(Database.BatchableContext BC){}

}

Abhinav Gupta said...

Adil, Can you please share the error logs also, with line numbers and everything.

&gt;&gt;&gt;@.D.i.L said...

Hi Abhinav

Here is my modified code. Now I dont get that erorr but there is only one record processed.


global class updateOpportunityStage implements Database.Batchable,Schedulable{
global string query ;

global updateOpportunityStage(){

Query = 'Select Id,BigMachines__Status__c from BigMachines__Quote__c' ;

}

global database.querylocator start(Database.BatchableContext BC){
return Database.getQueryLocator(query);
}
global void execute(SchedulableContext SC){
updateOpportunityStage stg = new updateOpportunityStage();
database.executebatch(stg);

}

global void execute(Database.BatchableContext BC, List scope){


Set liOppIds = new Set();
//List oppList = new List() ;
for(sObject s : scope){

BigMachines__Quote__c quote = (BigMachines__Quote__c)s;
// System.debug('Adil'+quote);
if(quote.BigMachines__Status__c == 'unison' && quote.BigMachines__Is_Primary__c == true)
liOppIds.add(quote.BigMachines__Opportunity__c);

}


//query all the opportunities in a single query
List opp = new List();
opp = [select id, StageName from Opportunity where id in :liOppIds and stagename != 'Closed Won'];
for ( Opportunity opps : opp)
{
opps.StageName = 'Closed Won' ;
}
//update all opportunities in a single DML
if(opp.size() > 0)
update opp;

}
global void finish(Database.BatchableContext BC){}

}

&gt;&gt;&gt;@.D.i.L said...

here is my debug log

23.0 APEX_CODE,DEBUG;APEX_PROFILING,INFO;CALLOUT,INFO;DB,INFO;SYSTEM,DEBUG;VALIDATION,INFO;VISUALFORCE,INFO;WORKFLOW,INFO
12:00:01.025 (25595000)|EXECUTION_STARTED
12:00:01.025 (25635000)|CODE_UNIT_STARTED|[EXTERNAL]|01pV00000000KdY|updateOpportunityStage
12:00:01.050 (50925000)|SYSTEM_METHOD_ENTRY|[15]|BatchableContextImpl.BatchableContextImpl()
12:00:01.050 (50952000)|SYSTEM_METHOD_EXIT|[15]|BatchableContextImpl
12:00:01.051 (51996000)|METHOD_ENTRY|[1]|01pV00000000KdY|updateOpportunityStage.updateOpportunityStage()
12:00:01.052 (52014000)|METHOD_EXIT|[1]|updateOpportunityStage
12:00:01.052 (52084000)|SYSTEM_METHOD_ENTRY|[11]|Database.getQueryLocator(String)
12:00:01.054 (54864000)|SOQL_EXECUTE_BEGIN|[11]|Aggregations:0|Select Id,BigMachines__Status__c from BigMachines__Quote__c
12:00:01.195 (195089000)|SOQL_EXECUTE_END|[11]|Rows:91
12:00:01.195 (195134000)|SYSTEM_METHOD_EXIT|[11]|Database.getQueryLocator(String)
12:00:01.203 (203187000)|SYSTEM_METHOD_ENTRY|[7]|QueryLocatorIterator.QueryLocatorIterator()
12:00:01.203 (203215000)|SYSTEM_METHOD_EXIT|[7]|QueryLocatorIterator
12:00:01.422 (249266000)|CUMULATIVE_LIMIT_USAGE
12:00:01.422|LIMIT_USAGE_FOR_NS|(default)|
Number of SOQL queries: 0 out of 200
Number of query rows: 0 out of 50000
Number of SOSL queries: 0 out of 20
Number of DML statements: 0 out of 150
Number of DML rows: 0 out of 10000
Number of script statements: 1 out of 1000000
Maximum heap size: 0 out of 6000000
Number of callouts: 0 out of 0
Number of Email Invocations: 0 out of 10
Number of fields describes: 0 out of 100
Number of record type describes: 0 out of 100
Number of child relationships describes: 0 out of 100
Number of picklist describes: 0 out of 100
Number of future calls: 0 out of 0

12:00:01.422 (249266000)|CUMULATIVE_LIMIT_USAGE_END

12:00:01.249 (249318000)|CODE_UNIT_FINISHED|updateOpportunityStage
12:00:01.249 (249332000)|EXECUTION_FINISHED

Abhinav Gupta said...

Its not coming for me Adil, I tried running with an Object with empty fields.

I would suggest one thing, try changing Apex class versions to latest or backwards. This issues seems to be coming randomly

Here is the code :

global class updateOpportunityStage implements Database.Batchable,Schedulable{

global updateOpportunityStage(){

}

global database.querylocator start(Database.BatchableContext BC){
return Database.getQueryLocator('Select Name,Id from Specimen__c');
}

global void execute(SchedulableContext SC){
updateOpportunityStage stg = new updateOpportunityStage();
database.executebatch(stg);

}

global void execute(Database.BatchableContext BC, List scope){
System.debug(LoggingLevel.INFO, '>>> ' + scope );
}


global void finish(Database.BatchableContext BC){}


}