First your core logic of checking if a record is success or not is incorrect. . As it will always create junk `Order_Integration__c` records for each `upsertOrderList`. As per your code lets say if first result in `lsr` is not successful, so the code will go ahead iterate on all `upsertOrderList` and create `Order_Integration__c` records for all `upsertOrderList` in else condition. For checking if a upsert has passed successfully or not check [this][1] As explained here order of the result and the upsert records will be always same. for (Integer index = 0; index < lsr.size(); index++) { error = ''; errorRecCount = 0; if(lsr[index].isSuccess() && lsr[index].isCreated()){ if (lsr[index].isCreated()) { successRecCount++; } orderIdSet.add(lsr[index].getId()); } else { hasErrors = true; for (Database.Error err : lsr[index].getErrors()) { errorRecCount++; Order_Integration__c ordInt = new Order_Integration__c(); ordInt.Id = upsertOrderList[index].Order_Integration_Ids__c; error += err.getStatusCode() + ': ' + err.getMessage(); error += 'Fields: ' + err.getFields(); error += '\n'; ordInt.Status_Description__c = error; ordIntegrationList.add(ordInt); if (err.getStatusCode() == system.StatusCode.REQUIRED_FIELD_MISSING) { if (errorMap.containsKey(err.getMessage())) { count = errorMap.get(err.getMessage()); errorMap.put(err.getMessage(), ++count); } else { errorMap.put(err.getMessage(), 1); } } } } } [1]: https://salesforce.stackexchange.com/questions/189138/database-upsert-how-to-find-which-records-have-inserted-updated-based-on-exter For the exception , I believe you are initializing `error = ''` and `errorRecCount` in wrong place. It should be inside the for loop like shown above. The issue is as you have initialized `String` at top and concatenating it always, so there might be scenario in your batch where that `String` is growing beyond 12MB of heap limit.