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;
}
}
ProductController
method could return a null object or adding the code ofProductController
class. I.E. ifProductController.getProducts()
could returnnull
, you should enclose the for loop overoneLinkResult
in anif
blockGetProductsBatch
class and focus only onProductController
since there happens the error. Please edit the question accordingly.