Context
You create two Account records in Salesforce, a1 and a2. You set a2 as the parent of a1 (a1.ParentId = a2.Id). Then you try to make a1 the parent of a2 (a2.ParentId = a1.Id). What happens?
Short answer
Salesforce prevents circular parent-child relationships. Once a2 is the parent of a1, an attempt to make a1 the parent of a2 is blocked and the platform returns a validation or API error. Salesforce enforces the Account hierarchy as a directed acyclic graph (a tree), so cycles are not allowed.
Why Salesforce blocks this
A parent-child cycle would create an infinite loop in hierarchy traversals (ancestors and descendants) and break assumptions used by UI features such as Parent Account and Account Hierarchy, along with sharing calculations, rollups, and many other system operations. Enforcing a strict non-recursive parent chain is how Salesforce keeps data integrity and predictable behavior.
How the error appears
In the Salesforce UI you will typically get an error when saving the Account record, telling you a recursive or circular relationship is not allowed. Through the API (SOAP or REST) or Apex, the save or update fails with an error indicating a recursive relationship or an invalid ParentId update. The exact wording varies across releases and APIs, but the effect is the same: the transaction is rejected.
How to detect and prevent circular references programmatically
If you need to enforce this yourself, or to catch potential cycles early for bulk updates, integrations, or friendlier messages, add a server-side check (an Apex trigger or a before-save flow) that walks the parent chain and confirms the new parent is not a descendant of the current record.
Example: simple Apex pre-check (conceptual)
// Pseudocode - do NOT paste to production without testing
for (Account a : Trigger.new) {
Id newParentId = a.ParentId;
if (newParentId == null) continue;
// Walk up the parent chain from newParentId and look for the current account Id
Id cursor = newParentId;
while (cursor != null) {
if (cursor == a.Id) {
a.addError('Cannot set parent: this would create a circular account hierarchy.');
break;
}
// Query parent of cursor
Account p = [SELECT ParentId FROM Account WHERE Id = :cursor LIMIT 1];
cursor = p.ParentId;
// Optional: add safety counter to avoid infinite loops
}
}
Two things to watch in real code. Use bulk-safe patterns and avoid one SOQL per loop: collect parent Ids, fetch the accounts in batches, then iterate in memory. For very deep hierarchies or large data volumes, use an iterative algorithm that loads parent levels in chunks so you stay within governor limits.
Best practices
- Enforce the check in a before-insert or before-update Apex trigger, or a server-side Flow, to block cycles.
- Give users clear UI validation messages so they understand why the save failed.
- When doing data loads, validate parent chains in your ETL or pre-processing step and catch cycles before they reach Salesforce.
- If you need fast ancestor or descendant queries, consider storing computed hierarchy metadata (e.g., depth, ancestor list), and make sure the metadata update logic prevents cycles too.
Keywords
Salesforce account hierarchy, circular parent relationship, ParentId, recursive relationship, Apex trigger, account parent-child, data integrity
Leave a Comment