0

I have an issue with an Apex class returning this "First error: Attempt to de-reference a null object"

This is the original apex class which is running in production and was implemented by a Salesforce partner which was working for over a year.

public class GetProductsBatch implements Database.Batchable<String>, Database.AllowsCallouts {
    public IntegrationParams__c params;
    public AuthTokens__c tokens;

    public GetProductsBatch(IntegrationParams__c params, AuthTokens__c tokens) {
        this.params = params;
        this.tokens = tokens;
    }

    public Iterable<String> start(Database.BatchableContext bc) {
        List<String> urlsList = ProductController.getProductsUrls(params, tokens);
        return urlsList;
    }

    public void execute(Database.BatchableContext bc, List<String> values){
        List<Product2> productsToUpsert = new List<Product2>();
        for(String item: values){
            List<Product2> oneLinkResult = ProductController.getProducts(item, params, tokens);

            for(Product2 prod: oneLinkResult){
                productsToUpsert.add(prod);
            }
        }

        List<Product2> upsertItems = ProductController.updateProduct(productsToUpsert);
        upsert upsertItems;
    }

    public void finish(Database.BatchableContext bc){
        if(!Test.isRunningTest()){
            PriceBookController.getPriceBook();

            GetStandardPBEnrtyBatch standardEntryBatchObj = new GetStandardPBEnrtyBatch(params, tokens);
            Id batchId = Database.executeBatch(standardEntryBatchObj, 1);
        }
    }
}

The error in the production class is this caused by: System.NullPointerException: Attempt to de-reference a null object Class.ProductController.getProducts: line 59, column 1 (Object availabilityObj = productAvailabilityMap.get(productId);) Class.GetProductsBatch.execute: line 18, column 1 (List<Product2> oneLinkResult = ProductController.getProducts(item, params, tokens);)

After looking in to multiple forum posts it was recommended to add a null check in the class which I eventually managed to do as I have no coding experience and used this code in my sandbox which also returns the same error even though it has the null check in

public class GetProductsBatch implements Database.Batchable<String>, Database.AllowsCallouts {
public IntegrationParams__c params;
public AuthTokens__c tokens;

public GetProductsBatch(IntegrationParams__c params, AuthTokens__c tokens) {
    this.params = params;
    this.tokens = tokens;
}

public Iterable<String> start(Database.BatchableContext bc) {
    List<String> urlsList = ProductController.getProductsUrls(params, tokens);
    return urlsList;
}

public void execute(Database.BatchableContext bc, List<String> values){
    List<Product2> productsToUpsert = new List<Product2>();
    for(String item: values){
        List<Product2> oneLinkResult = ProductController.getProducts(item, params, tokens);

        for(Product2 prod: oneLinkResult){
            productsToUpsert.add(prod);
        }
    }

    List<Product2> upsertItems = ProductController.updateProduct(productsToUpsert);
    
    if (upsertItems != null && !upsertItems.isEmpty()) {
        Database.upsert(upsertItems, Product2.ExternalId_2__c, false);
    }
}

public void finish(Database.BatchableContext bc){
    if(!Test.isRunningTest()){
        PriceBookController.getPriceBook();

        GetStandardPBEnrtyBatch standardEntryBatchObj = new GetStandardPBEnrtyBatch(params, tokens);
        Id batchId = Database.executeBatch(standardEntryBatchObj, 1);
    }
}

}

Any help on what I would need to add or remove from the code would be appreciated or Is there a way I can see what item is casing the null ref.

Edit: This is the production code for the Product controller

public with sharing class ProductController {

    public static List<Product2> getProducts(String urlPart, IntegrationParams__c params, AuthTokens__c tokens){
        List<Product2> productToUpsert = new List<Product2>();
        String urlEnd = '/product-service' + urlPart;
        Map<String, Object> responseBody = ApiCalloutService.getBrightpearlObject(urlEnd, params, tokens);
        List<Object> productsList = (List<Object>)responseBody.get('response');

        Set<String> productTypeIds = new Set<String>();
        Set<String> productIds = new Set<String>();
        for(Object obj: productsList){
            Map<String, Object> productMap = (Map<String, Object>)obj;
            String productTypeId = String.valueOf(productMap.get('productTypeId'));
            productTypeIds.add(productTypeId);

            String productId = String.valueOf(productMap.get('id'));
            productIds.add(productId);
        }
        Map<String, String> productTypeMap = ProductController.getProductType(params, tokens, productTypeIds);
        Map<String, Object> productAvailabilityMap = ProductController.getProductAvailability(params, tokens, productIds);

        for(Object obj: productsList){
            Map<String, Object> productMap = (Map<String, Object>)obj;

            /* ExternalId and ProductCode */
            String productExternalId = String.valueOf(productMap.get('id'));
            String productCode = String.valueOf(productMap.get('id'));

            /* StockKeepingUnit */
            Map<String, Object> identity = (Map<String, Object>)productMap.get('identity');
            String productSKU = String.valueOf(identity.get('sku'));
            
            /* Name and Description */
            List<Object> salesChannelsList = (List<Object>)productMap.get('salesChannels');
            Map<String, Object> salesChannels = (Map<String, Object>)salesChannelsList[0];
            String productName = String.valueOf(salesChannels.get('productName'));

            Map<String, Object> shortDescription = (Map<String, Object>)salesChannels.get('shortDescription');
            String productDescription = '';
            String longDescription = String.valueOf(shortDescription.get('text'));
            if(longDescription.length() >= 4000){
                productDescription = longDescription.substring(0, 4000); 
            } else {
                productDescription = String.valueOf(shortDescription.get('text'));
            }
            String openP = productDescription.replaceAll('<p>', '');
            String closeP = openP.replaceAll('</p>', '');
            String openLI = closeP.replaceAll('<li>', '');
            String closeLI = openLI.replaceAll('</li>', '');
            String openUL = closeLI.replaceAll('<ul>', '');
            String filtredDescription = openUL.replaceAll('</ul>', '');

            /* Family */
            String productTypeId = String.valueOf(productMap.get('productTypeId'));
            String productFamily = productTypeMap.get(productTypeId);

            /* In Stock and On Hand */
            String productId = String.valueOf(productMap.get('id'));
            Object availabilityObj = productAvailabilityMap.get(productId);
            Map<String, Object> availabilityMap = (Map<String, Object>)availabilityObj;
            String productInStock = '0';
            String productOnHand = '0';
            if(availabilityMap != null){
                Map<String, Object> total = (Map<String, Object>)availabilityMap.get('total');
                productInStock = String.valueOf(total.get('inStock'));
                productOnHand = String.valueOf(total.get('onHand'));
            }

            Product2 newProduct = new Product2();
            newProduct.ProductCode = productCode;
            newProduct.ExternalId = productExternalId;
            newProduct.StockKeepingUnit = productSKU;
            newProduct.Name = productName;
            newProduct.Family = productFamily;
            newProduct.In_Stock__c = Integer.valueOf(productInStock);
            newProduct.On_Hand__c = Integer.valueOf(productOnHand);
            newProduct.Description = filtredDescription;
            productToUpsert.add(newProduct);
        }

        return productToUpsert;
    }

    public static List<Product2> updateProduct(List<Product2> productsToUpsert){
        /* relate product by ExternalId */
        List<String> externalIds = new List<String>();
        for(Product2 prod: productsToUpsert){
            externalIds.add(prod.ExternalId);
        }

        List<Product2> productsFromOrg = [
            SELECT Id, Name, ProductCode, ExternalId, StockKeepingUnit, 
            Family, In_Stock__c, On_Hand__c, Description
            FROM Product2 
            WHERE ExternalId IN :externalIds
            WITH SECURITY_ENFORCED
        ];

        for(Product2 prodToUpsert: productsToUpsert){
            for(Product2 prodFromOrg: productsFromOrg){
                if(prodToUpsert.ExternalId == prodFromOrg.ExternalId){
                    prodToUpsert.Id = prodFromOrg.Id;
                }
            }
        }

        /* delete Brightpearl duplicates from list */
        Set<String> potentiallyNewSKU = new Set<String>();
        for(Product2 prodToUpsert: productsToUpsert){
            if(prodToUpsert.Id == null){
                potentiallyNewSKU.add(prodToUpsert.StockKeepingUnit);
            }
        }

        List<Product2> potentiallyFromOrg = [
            SELECT Id, StockKeepingUnit 
            FROM Product2 
            WHERE StockKeepingUnit IN :potentiallyNewSKU
            WITH SECURITY_ENFORCED
        ];

        for(Integer i=productsToUpsert.size()-1; i>=0; i--){
            for(Product2 prodFromOrg: potentiallyFromOrg){
                if(productsToUpsert[i].StockKeepingUnit == prodFromOrg.StockKeepingUnit && productsToUpsert[i].Id == null){
                    Product2 removedProd = productsToUpsert.remove(i);
                    break;
                }
            }
        }

        Map<String, Product2> uniqueProductMap = new Map<String, Product2>();
        for(Product2 prod: productsToUpsert){
            uniqueProductMap.put(prod.StockKeepingUnit, prod);
        }

        return uniqueProductMap.values();
    }

    public static Map<String, Object> getProductAvailability(IntegrationParams__c params, AuthTokens__c tokens, Set<String> productIds){
        Map<String, Object> resultMap = new Map<String, Object>();

        String urlPart = '';
        for(String item: productIds){
            urlPart = urlPart + item + '.';
        }
        String urlPartUpdated = urlPart.removeEnd('.');
        String urlEnd = '/warehouse-service/product-availability/' + urlPartUpdated;
        Map<String, Object> responseBody = ApiCalloutService.getBrightpearlObject(urlEnd, params, tokens);

        Object responseObject = (Object)responseBody.get('response');
        resultMap = (Map<String, Object>)responseObject;
        return resultMap;
    }

    public static Map<String, String> getProductType(IntegrationParams__c params, AuthTokens__c tokens, Set<String> productTypeIds){
        Map<String, String> resultMap = new Map<String, String>();

        String urlPart = '';
        for(String item: productTypeIds){
            urlPart = urlPart + item + '.';
        }
        String urlPartUpdated = urlPart.removeEnd('.');
        String urlEnd = '/product-service/product-type/' + urlPartUpdated;
        Map<String, Object> responseBody = ApiCalloutService.getBrightpearlObject(urlEnd, params, tokens);

        List<Object> typesList = (List<Object>)responseBody.get('response');
        for(Object obj: typesList){
            Map<String, Object> typeMap = (Map<String, Object>)obj;
            String typeId = String.valueOf(typeMap.get('id'));
            String typeName = String.valueOf(typeMap.get('name'));
            resultMap.put(typeId, typeName);
        }

        return resultMap;
    }

    public static List<String> getProductsUrls(IntegrationParams__c params, AuthTokens__c tokens){
        Map<String, Object> responseBody = ApiCalloutService.optionsBrightpearlObject('/product-service/product/', params, tokens);
        Map<String, Object> getUrisMap = (Map<String, Object>)responseBody.get('response');
        List<Object> uris = (List<Object>)getUrisMap.get('getUris');

        List<String> result = new List<String>();
        for(Object obj: uris){
            result.add(String.valueOf(obj));
        }

        return result;
    }
} 
4
  • Welcome, what's the line throwing the exception? You should edit the question either telling us which ProductController method could return a null object or adding the code of ProductController class. I.E. if ProductController.getProducts() could return null, you should enclose the for loop over oneLinkResult in an if block
    – RubenDG
    Jun 28 at 9:26
  • The error in the production class is this caused by: System.NullPointerException: Attempt to de-reference a null object Class.ProductController.getProducts: line 59, column 1 (Object availabilityObj = productAvailabilityMap.get(productId);) Class.GetProductsBatch.execute: line 18, column 1 (List<Product2> oneLinkResult = ProductController.getProducts(item, params, tokens);) Jun 28 at 9:41
  • Ok, so we could skip the whole GetProductsBatch class and focus only on ProductController since there happens the error. Please edit the question accordingly.
    – RubenDG
    Jun 28 at 9:44
  • so the Get product class look ok? And I need to edit the question to show the product controller class? Jun 28 at 9:51

1 Answer 1

0

If a NPE happens on line Object availabilityObj = productAvailabilityMap.get(productId);, the map productAvailabilityMap is null.
You get that map from the method ProductController.getProductAvailability(), in which there are the following lines:

Object responseObject = (Object)responseBody.get('response');
resultMap = (Map<String, Object>)responseObject;

Since the method returns null it means that responseBody doesn't hold the map.

To fix the issue you should add a null check before using productAvailabilityMap:

/* In Stock and On Hand */
String productInStock = '0';
String productOnHand = '0';
String productId = String.valueOf(productMap.get('id'));
if (productAvailabilityMap != null) {
    Object availabilityObj = productAvailabilityMap.get(productId);
    Map<String, Object> availabilityMap = (Map<String, Object>) availabilityObj;
    if (availabilityMap != null) {
        Map<String, Object> total = (Map<String, Object>) availabilityMap.get('total');
        productInStock = String.valueOf(total.get('inStock'));
        productOnHand = String.valueOf(total.get('onHand'));
    }
}

If the Api Version of the class is equal or greater than 50 (Winter 21), you could use Safe Navigation Operator (?.) instead of an explicit check for null reference:

/* In Stock and On Hand */
String productInStock = '0';
String productOnHand = '0';
String productId = String.valueOf(productMap.get('id'));
Object availabilityObj = productAvailabilityMap?.get(productId); // Safe Navigation Operator
Map<String, Object> availabilityMap = (Map<String, Object>) availabilityObj;
if (availabilityMap != null) {
    Map<String, Object> total = (Map<String, Object>) availabilityMap.get('total');
    productInStock = String.valueOf(total.get('inStock'));
    productOnHand = String.valueOf(total.get('onHand'));
}
3
  • Thank you for this I will look to add this in. Jun 28 at 11:15
  • Thank you @RubenDG this worked( I would mark the answer as useful but It won't allow due to rep level) Jun 28 at 12:45
  • @StevenOdling You're welcome. Glad it helped.
    – RubenDG
    Jun 28 at 12:51

You must log in to answer this question.

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