SfSkills Domains Docs GitHub

Home / apex / apex-record-clone-patterns

apex-record-clone-patterns

SObject.clone(preserveId, isDeepClone, preserveReadonly, preserveAutonumber): shallow vs deep clone semantics, related-record replication, clone with parent repointing, autonumber preservation. NOT for data migration — use data/bulk-api-and-large-data-loads. NOT for record snapshots — use data/field-history-tracking.

Open the skill folder on GitHub


Apex Record Clone Patterns

Activate when duplicating an sObject record in Apex — one record with a new Id, a record with its children, or a snapshot with preserved audit fields. The SObject.clone() method has four boolean flags that control deep-vs-shallow, Id preservation, readonly-field behavior, and autonumber handling. Each has failure modes if misread.

Before Starting

Core Concepts

Method signature

public SObject clone(Boolean preserveId, Boolean isDeepClone, Boolean preserveReadonly, Boolean preserveAutonumber)

All four default to false. Typical invocations:

Account copy = acc.clone();                       // shallow, no Id, no readonly, new autonumber
Account copy = acc.clone(false, false, false, true);  // preserve autonumber
Account copy = acc.clone(true);                   // keep Id (for in-memory diffing)

Flags in detail

FlagMeaning
preserveIdClone has the same Id as original (rarely useful — can't insert a record with Id)
isDeepCloneCopies field values regardless of type (but NOT related records)
preserveReadonlyCopies CreatedDate, CreatedById, LastModifiedDate, SystemModstamp. Requires CreateAuditFields perm
preserveAutonumberClone carries the same autonumber value

"Deep clone" does NOT clone children

isDeepClone=true is a misnomer — it preserves formula/aggregate field values in the in-memory copy. It does NOT fetch and clone child records.

Cloning children requires explicit query + reparent

Opportunity opp = [SELECT Id, Name, (SELECT Id, Product2Id, Quantity, UnitPrice FROM OpportunityLineItems) FROM Opportunity WHERE Id = :srcId];
Opportunity newOpp = opp.clone(false, false, false, false);
newOpp.Name = 'Copy of ' + opp.Name;
insert newOpp;
List<OpportunityLineItem> newLines = new List<OpportunityLineItem>();
for (OpportunityLineItem oli : opp.OpportunityLineItems) {
    OpportunityLineItem copy = oli.clone();
    copy.OpportunityId = newOpp.Id;
    newLines.add(copy);
}
insert newLines;

Readonly-field preservation

Account a = [SELECT Id, Name, CreatedDate FROM Account WHERE Id = :id];
Account copy = a.clone(false, false, true, false);
insert copy;  // CreatedDate preserved — only if running user has CreateAuditFields permission

Perm gate: Setup → System Permissions → "Set Audit Fields upon Record Creation" must be enabled and permission assigned.

Common Patterns

Pattern: "Copy of" with new autonumber

Case c = [SELECT Id, Subject, Origin FROM Case WHERE Id = :id];
Case copy = c.clone();            // CaseNumber autonumber regenerated
copy.Subject = 'Copy of ' + c.Subject;
insert copy;

Pattern: Deep copy of Opportunity with line items

(See "Cloning children" code above.)

Pattern: Bulk clone 1→N

List<Account> src = [SELECT Id, Name FROM Account WHERE ...];
List<Account> copies = new List<Account>();
for (Account a : src) {
    for (Integer i = 0; i < 5; i++) {
        Account c = a.clone();
        c.Name = a.Name + ' v' + i;
        copies.add(c);
    }
}
insert copies;

Pattern: Clone via Database.insert with duplicate-rule bypass

Database.DMLOptions dmo = new Database.DMLOptions();
dmo.DuplicateRuleHeader.AllowSave = true;
List<Database.SaveResult> srs = Database.insert(copies, dmo);

Decision Guidance

ScenarioFlags
Duplicate a record, new Id, new autonumberclone()
Duplicate a record, preserve autonumberclone(false, false, false, true)
In-memory copy for comparison (no insert)clone(true, false, false, false)
Data migration preserving CreatedDateclone(false, false, true, false) + user perm
Duplicate record + childrenquery children, clone, reparent, insert parent first
  1. Identify whether children need to travel. If yes, query the subselect in the source SOQL.
  2. Clone the parent with the flags you need (usually clone() is sufficient).
  3. Insert the parent first to obtain the new Id.
  4. Clone each child, set the foreign key to the new parent Id, insert in bulk.
  5. For audit-field preservation, verify the running user has CreateAuditFields.
  6. Test with duplicate rules enabled AND disabled.
  7. Document the clone's semantic relationship to the source (e.g., store a Cloned_From__c lookup for traceability).

Review Checklist

Salesforce-Specific Gotchas

  1. clone() does NOT recursively clone children. isDeepClone=true preserves formula values but doesn't fetch relationships.
  2. preserveId=true followed by insert throws — you cannot insert with an Id. Only useful for in-memory diffing/asserting.
  3. preserveReadonly=true without CreateAuditFields silently ignores audit fields — the new record gets SYSTEM.now() timestamps, not the originals.
  4. OpportunityLineItem clone requires a Price Book Entry that exists on the target Opportunity — cloning across pricebooks needs repointing.
  5. Record Type Id is not cleared by clone — ensure target record type is valid for the cloned picklist values.

Output Artifacts

ArtifactDescription
Clone utility classReusable parent + children cloner
Traceability fieldCloned_From__c lookup to source
Permission docNote CreateAuditFields requirement for audit-preserving clones

Package files