For Developers

December 28, 2015

Understanding “Cannot Specify Id” on Insert Exception

Brenda Finn

If you have ever received one of the following Exceptions from Salesforce when performing an insert or upsert read on for insight:

  • Record ID: cannot specify Id in an insert call
  • Insert failed. First exception on row 0 with id a06g000000BALsHAAX; first error: INVALID_FIELD_FOR_INSERT_UPDATE, cannot specify Id in an insert call: [Id]

Problem

This error happens if you are attempting to insert a record with an Id which we would not do on purpose as we all know Salesforce generates the Id for you upon insertion in the database. So what gives?

Below is a scenario that will cause this error and had me stumped for too long. I’m sharing in hopes that it will save a fellow developer precious time.

Lets say you have a master object that you are upserting into the database in the same transaction as a list of its child objects that you also are upserting. You create a Visualforce page that allows the user to create or edit the master object and its set of child objects in one page. The user enters their data, adds 1 or more new child object and updates an existing child object and submits the form. However, during  the save method, a Validation Rule fails or there is an error within your transaction. So the user fixes the error (assuming it is a data issue fixable without leaving the current page) and thus keeping the same browser and hence controller data the same. They then resubmit the form assuming it will succeed.  But instead they are presented with the above exception. How is this possible you ask?

Once I figured it out, the answer is quite simple. When you start a transaction and perform your upsert and there is a failure, you rollback the changes in the database. Makes sense  so far. But Salesforce does not null out the object Ids for those that are being inserted within memory (the controller’s variables) so the objects inserted before the error will have an invalid Id. When the user submits the form after fixing the error, those records that were not inserted into the database but still have their Id field set will cause the above exception. So your data is in an inconsistent state.

Note: This will only happen with a custom Visualforce page because Salesforce takes care of this with the standard edit pages.

Solution

Assuming you have a try-catch block around your insert/upsert statements, a best practice so end-user does not get presented with a non-user friendly error message, you need to null out Ids in the objects you were trying to insert only.

Lets say you have a custom object called Parent and it has child objects called Child. You write a custom Visualforce page that allows the user to create or edit the Parent along with the existing Children objects or to create a new Child object. In order to track the Child objects in your list of objects to insert/upsert, there are two ways to handle it.

  • Create a map of Child objects where the key is a Boolean and the value is the list of Child objects. In your save method, you check each Child object to see if it has an Id. If it does, you add it to the Map with key=true, otherwise false. Then in your Catch block, you loop through the Map element with key=false and set the Id field to null. Here is the sample code.
public with sharing class ParentWithChildController {
public Parent__c parent {get; set;}
public List<Child__c> children {get; set;}

public ParentWithChildController() {
// set parent to input parameter
Id parentId = ApexPages.currentPage().getParameters().get('parentId');

if (parentId != null) {
parent = [select Id, Name from Parent__c where Id=:parentId limit 1];
} else {
parent = new Parent__c(Name='Parent');
}
children = [select Id, Name from Child__c where Parent__c=:parent.Id];
}

public PageReference save() {
SavePoint sp = Database.setSavepoint();
Map<Boolean, List<Child__c>> mapChildrenKeyedByExisting = new Map<Boolean, List<Child__c>>();
Boolean blnParentExists = parent.Id == null;
try {
upsert parent;
for (Child__c child : children) {
Boolean blnAlreadyExists = child.Id != null;

if (mapChildrenKeyedByExisting.get(blnAlreadyExists) == null) {
mapChildrenKeyedByExisting.put(blnAlreadyExists, new List<Child__c>());
}
mapChildrenKeyedByExisting.get(blnAlreadyExists).add(child);
}
upsert children;
} catch (Exception exc) {
String errorMsg = 'There was an error updating Parent and its associated Children. Details = ' +
exc.getMessage();
System.debug('==========> ' + errorMsg);
ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR, errorMsg));
/******** NULL Out the Parent Id if we were inserting it ********/
if (!blnParentExists) {
parent.Id = null;
}
for (Child__c child : mapChildrenKeyedByExisting.get(false)) {
/******** NULL Out the Child Id if we were inserting it ********/
if (mapChildrenKeyedByExisting.get(false) != null) {
child.Id = null;
}
}
// rollback any changes
Database.rollback(sp);
return null; // return to same page with error message
}
// on success return to Parent view page
return new PageReference('/' + parent.Id);
}
}
  • Create a ChildHelper object that has two properties: Child object and Boolean flag blnIsExisting. When the page first loads, create a ChildHelper object for each existing Child with blnIsExisting set to true. Then when the user creates a new Child, set blnIsExisting to false. Then in your catch block, you loop through your ChildHelper list and set Ids for all Helpers with an blnIsExisting value set to false. here is the sample code.
public with sharing class ParentWithChildController {
    public Parent__c parent {get; set;}
    public List&lt;ChildHelper&gt; helpers {get; set;}</pre>
public ParentWithChildController() {
// set parent to input parameter
Id parentId = ApexPages.currentPage().getParameters().get('parentId');

if (parentId != null) {
parent = [select Id, Name from Parent__c where Id=:parentId limit 1];
} else {
parent = new Parent__c(Name='Parent');
}

List&lt;Child__c&gt; lstChildren = [select Id, Name from Child__c where Parent__c=:parent.Id];
helpers = new List&lt;ChildHelper&gt;();
for (Child__c child : lstChildren) {
helpers.add(new ChildHelper(child, true));
}
}

public PageReference save() {
SavePoint sp = Database.setSavepoint();
Boolean blnParentExists = parent.Id == null;
try  {
upsert parent;

List&lt;Child__c&gt; lstChildrenToAdd = new List&lt;Child__c&gt;();
for (ChildHelper helper : helpers) {
// now associate the Parent with its Children
helper.child.Parent__c = parent.Id;
lstChildrenToAdd.add(helper.child);
}
upsert lstChildrenToAdd;
}
catch (Exception exc) {
String errorMsg = 'There was an error updating Parent and its associated Children. Details = ' + exc.getMessage();
System.debug('==========&gt; ' + errorMsg);
ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR, errorMsg));

for (ChildHelper helper : helpers) {
if (!helper.blnIsExisting) {
/******** NULL Out the Child Id if we were inserting it ********/
helper.child.Id = null;
}
}
if (!blnParentExists) {
/******** NULL Out the Parent Id if we were inserting it ********/
parent.Id = null;
}
return null;
}
return new PageReference('/' + parent.Id);
}

public class ChildHelper {
public Child__c child {get; set;}
public Boolean blnIsExisting {get; set;}

public ChildHelper(Child__c child, Boolean blnIsExisting) {
this.child = child;
this.blnIsExisting = blnIsExisting;
}
}
}

So there you have it. I hope this proves to be useful to those of you who may be struggling with this issue and save you some precious development time. Happy Coding!

About the Author

Brenda Finn

Brenda is a Sr. Force.com Developer at OpFocus where she brings over 15 years of experience in all facets of project and application development for building enterprise applications with her knowledge of Java, Apex, HTML, JavaScript and other technologies. She specializes in Data Analysis, Problem Resolution and Interpersonal Relations being detailed-oriented and a team player. Brenda holds a Bachelor's in Mathematics and Computer Science from James Madison University and is a Certified Salesforce Developer.

Related Posts