1

I'm building a method to update two date fields when a different date field is edited. When I update one of the date fields or both of the fields the error in the debug log is "Attempt to de-reference a null object".

My method looks like this:

private void updateRenewalDates(Map<Id, OpportunityLineItem> newMap, Map<Id, OpportunityLineItem> oldMap)
{
    // Create Lists to hold data
    List<OpportunityLineItem> renewals = new List<OpportunityLineItem>();
    Map<Id,List<OpportunityLineItem>> oppToLineItem = new Map<Id,List<OpportunityLineItem>>();

    // Loop through filter records from the api call and see if they meet the criteria
    System.debug('list of renewals is: ' + renewals); // Should be blank
    System.debug('Map of oppToLineItem is ' + oppToLineItem); // Should be blank
    System.debug('newMap is ' + newMap); // Should have a value
    System.debug('oldMap is ' + oldMap); // Should have a vlaue

    for(OpportunityLineItem oli:newMap.values())
    {
        if((oli.Start_Date__c != oldMap.get(oli.id).Start_Date__c) || (oli.End_Date__c != oldMap.get(oli.id).End_Date__c))
        {
    System.debug('Map of oppToLineItem is ' + oppToLineItem); // Should be blank        
            oppToLineItem.get(oli.Id).add(oli);
    System.debug('Map of oppToLineItem is ' + oppToLineItem); // Should not blank       
        }
    }

    // Get the record fields for records where the id is from our list
    renewals = [SELECT Id, Start_Date__c, Start_Date_Renewal__c, End_Date__c, End_Date_Renewal__c, Opportunity.ContractTerm__c 
                FROM OpportunityLineItem 
                WHERE Id in:oppToLineItem.keyset()];

    // Loop through results of the query and update the record with the new value       
    for(OpportunityLineItem oppLineItem:renewals)
    {
        List<OpportunityLineItem> olis = oppToLineItem.get(oppLineItem.OpportunityId);
        for(OpportunityLineItem line:olis)
        {
    // Update Renewal dates with the start/end date + Contract length       
            line.Start_Date_Renewal__c = line.Start_Date__c.addMonths(Integer.valueOf(line.Opportunity.ContractTerm__c));
            line.End_Date_Renewal__c = line.End_Date__c.addMonths(Integer.valueOf(line.Opportunity.ContractTerm__c));
        }
    }

}

More specifically I think the error is in this chunk:

 for(OpportunityLineItem oli:newMap.values())
    {
        if((oli.Start_Date__c != oldMap.get(oli.id).Start_Date__c) || (oli.End_Date__c != oldMap.get(oli.id).End_Date__c))
        {
    System.debug('Map of oppToLineItem is ' + oppToLineItem); // Should be blank        
            oppToLineItem.get(oli.Id).add(oli);
    System.debug('Map of oppToLineItem is ' + oppToLineItem); // Should not blank       
        }
    }

In my debug log it shows that the map oppToLineItem is null, hence the de-reference a null object. However, I can also see the newMap and oldMap and the value is different as I expect in my criteria. How come then the record does not get added to the Map if it does meet the criteria?

2
  • 1
    What's the line number in that error message that it is logging? And where is that specific line in the code you posted above?
    – Mark Pond
    Commented Aug 16, 2016 at 19:47
  • the line is 139 which is the oppToLineItem.get(oli.Id).add(oli); Commented Aug 16, 2016 at 20:33

2 Answers 2

2

You're doing two things at once: filtering and grouping. The grouping is where you're going astray.

Here's what you are doing in a more expanded format.

Map<Id, List<OpportunityLineItem>> byParent = new Map<Id, List<OpportunityLineItem>>();
for (OpportunityLineItem item : trigger.new)
{
    if (/*condition*/)
    {
        List<OpportunityLineItem> lineItems = byParent.get(item.Id);
        // lineItems is null because you never put anything in the map
        // so of course you can't add anything to it yet

        lineItems.add(item); // will throw NPE
    }
}

Here's how the grouping part should look.

Map<Id, List<OpportunityLineItem>> byParent = new Map<Id, List<OpportunityLineItem>>();
for (OpportunityLineItem item : trigger.new)
{
    if (!byParent.contains(item.OpportunityId))
        byParent.put(item.OpportunityId, new List<OpportunityLineItem>());
    List<OpportunityLineItem> lineItems = byParent.get(item.OpportunityId);
    // now you've put something in the map
    // you always know this list will be non-null

    lineItems.add(item); // will not throw NPE
}

The filtering would be best done separately:

List<OpportunityLineItem> dateRangeChanged = new List<OpportunityLineItem>();
for (OpportunityLineItem item : trigger.new)
{
    if (/*condition*/) dateRangeChanged.add(item);
}

You can make this functionality more reusable by moving it into separate, static methods.

public static void List<OpportunityLineItem> hasDateRangeChanged
    (List<OpportunityLineItem> newRecords, Map<Id, OpportunityLineItem> oldMap)
{
    // implementation
}
public static void Map<Id, List<OpportunityLineItem>> groupByParent
    (List<OpportunityLineItem> items)
{
    // implementation
}

Then in your trigger, you can just do:

Map<Id, List<OpportunityLineItem>> byParent = OliService.groupByParent(
    OliService.hasDateRangeChanged(trigger.new, trigger.oldMap)
);
3
  • What? you lost me. Commented Aug 16, 2016 at 20:49
  • We can also chat if you're still lost.
    – Adrian Larson
    Commented Aug 16, 2016 at 21:35
  • can we chat about this? Commented Aug 17, 2016 at 14:11
2

Adrian's answer is a great solution.

Here's another approach, which will specifically fix the null pointer exception that's being thrown. I didn't look into the logic of your implementation to spot any issues with it.

Your code assumes that there's a list already in the map tied to the key, but you never instantiated a list to be in the map - you've only defined the map itself.

if (criteria) {
    System.debug('Map of oppToLineItem is ' + oppToLineItem); // Should be blank        
    // this line assumes that there is already a list<> in this map when
    // you are calling .add()
    oppToLineItem.get(oli.Id).add(oli);
    System.debug('Map of oppToLineItem is ' + oppToLineItem); // Should not blank
}

A basic solution would be to check for null before calling .add() and if it's null, initialize the list in the map at that point.

if (criteria) {
    System.debug('Map of oppToLineItem is ' + oppToLineItem); // Should be blank        
    List<OpportunityLineItem> items = oppToLineItem.get(oli.Id);

    // check to see if the list exists already
    if (items == null) {

        // initialize a new list and add the current item to it during construction of the list
        oppToLineItem.put(oli.OpportunityId, new List<OpportunityLineItem>{oli});
    } else {

        // get the existing list and add the item to it
        oppToLineItem.get(oli.id).add(oli);
    }
    System.debug('Map of oppToLineItem is ' + oppToLineItem); // Should not blank
}
3
  • 1
    Also the map's keys should be the OpportunityId rather than the line item id for proper operation. I'm leaving the example above as-is, but this is pretty important. :-)
    – Mark Pond
    Commented Aug 17, 2016 at 3:57
  • Hmm I just noticed you actually don't use a key at all! You do Map.put(List). This strategy was how I started grouping, I switched over after some time reading others' code.
    – Adrian Larson
    Commented Aug 17, 2016 at 14:39
  • 1
    @AdrianLarson thanks for catching the syntax error on the Map.put(), unfortunately it won't automagically pull out the key from the sobject's id field using that syntax and my previous implementation wouldn't actually compile.
    – Mark Pond
    Commented Aug 17, 2016 at 16:00

You must log in to answer this question.

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