SfSkills Domains Docs GitHub

Home / apex / apex-savepoint-and-rollback

apex-savepoint-and-rollback

Database.Savepoint / Database.rollback for partial-transaction undo: placement rules, ID reset, limit counters, nested savepoints, rollback after callout. NOT for Database.allOrNone=false partial success semantics — use apex/apex-dml-patterns. NOT for Queueable chained rollback — use apex/apex-queueable-patterns.

Open the skill folder on GitHub


Apex Savepoint and Rollback

Activate when multi-step DML needs a safety net: if a later step fails, earlier steps should be undone. Database.Savepoint + Database.rollback(sp) is Salesforce's mechanism. The semantics are subtle: IDs on in-memory records are NOT reset, governor limits are not fully reset, and rollback after a callout is forbidden.

Before Starting

Core Concepts

The savepoint lifecycle

Savepoint sp = Database.setSavepoint();
try {
    insert accounts;
    insert contacts;
    insert opportunities;
} catch (Exception e) {
    Database.rollback(sp);
    // handle
}

setSavepoint() snapshots the transaction; rollback(sp) undoes DML after the savepoint.

What rollback undoes

What rollback does NOT undo

Nested savepoints

Valid; each call to setSavepoint() creates a new savepoint. Rolling back an outer savepoint invalidates inner ones.

Limits

Database.setSavepoint counts against the 150 DML-statement limit. Database.rollback also counts. Avoid savepoint-per-record loops.

Common Patterns

Pattern: All-or-nothing multi-DML

Savepoint sp = Database.setSavepoint();
try {
    insert parents;
    insert children;  // if this fails, parents rolled back too
} catch (DmlException e) {
    Database.rollback(sp);
    throw new MyException('Operation aborted', e);
}

Pattern: Rollback-then-rethrow with clean IDs

Savepoint sp = Database.setSavepoint();
try {
    insert accs;
    insert opps;
} catch (Exception e) {
    Database.rollback(sp);
    for (Account a : accs) a.Id = null;  // so retries can re-insert
    throw e;
}

Pattern: Pre-callout DML with deferred rollback decision

Rollback must happen before any callout. If you need conditional rollback based on callout response, do DML AFTER the callout, or don't use savepoints at all — use a compensating action.

Decision Guidance

SituationApproach
Multi-step DML, all or nothingSavepoint + try/catch + rollback
Partial success acceptableDatabase.insert(records, false)
DML + callout + conditional rollbackCallout first, then DML (no savepoint needed)
Nested service-layer rollbackCaller sets savepoint; callees rely on it
  1. Identify the transaction boundary — where savepoint belongs.
  2. Place Database.setSavepoint() before the first reversible DML.
  3. Wrap remaining DML in try/catch; rollback in catch.
  4. Null out IDs on in-memory records if retry is planned.
  5. Verify NO callouts occur between savepoint and rollback.
  6. Count savepoint + rollback against DML limit (each costs 1).
  7. Test failure paths with forced exceptions to confirm rollback works.

Review Checklist

Salesforce-Specific Gotchas

  1. Rollback after callout throws CalloutException. Order matters: DML-savepoint-DML-rollback OR callout-then-DML, never mix.
  2. IDs persist on in-memory records after rollback. Database state is undone; Apex objects still carry IDs — re-inserting throws "Id already exists" if you don't null them.
  3. SOQL query count is NOT reset by rollback. Only DML row counts partially reset. A rollback loop can still blow the SOQL-101 limit.

Output Artifacts

ArtifactDescription
Transaction-boundary designWhere savepoints live in the service layer
Rollback helperUtility that rolls back + nulls IDs
Test coverageForce-fail case proving rollback

Package files