Skip to content
Salesforce Dictionary - Free Salesforce GlossarySalesforce Dictionary
DictionaryTTransaction, Apex
DevelopmentAdvanced

Transaction, Apex

An Apex Transaction is the single unit of work the Salesforce platform tracks from the moment Apex starts executing in response to a request (a record save, an inbound API call, a scheduled job, a flow action) until that request returns to the caller.

§ 01

Definition

An Apex Transaction is the single unit of work the Salesforce platform tracks from the moment Apex starts executing in response to a request (a record save, an inbound API call, a scheduled job, a flow action) until that request returns to the caller. Every governor limit in Apex (SOQL queries, DML rows, CPU time, heap size, callouts, future jobs) is enforced per transaction; when the transaction ends, the counters reset.

A transaction either commits or rolls back as a single atomic unit. If any uncaught exception escapes the Apex code, every DML operation in the transaction is rolled back automatically. If the code reaches the end without an uncaught exception, the platform commits all DML at once. Developers can also place explicit Savepoints inside a transaction and roll back to a savepoint without aborting the whole call, which is the only way to do partial rollback in Apex.

§ 02

How the Apex transaction boundary shapes every governor limit and DML decision

One request, one transaction

Salesforce starts a fresh Apex transaction each time a request enters the platform. A REST API call, a record save through the UI, a Process Builder run, a Flow invocation, a Queueable job, a scheduled Apex run, and an Email Service invocation are each their own transaction. Two saves from the same UI screen are two transactions. A trigger that calls a future method opens a second transaction when the future job actually runs, not at the moment of the call. The boundary determines what code shares state, what code shares limits, and what code can roll back together.

Governor limits reset at the boundary

Salesforce enforces every Apex limit per transaction. 100 SOQL queries, 150 DML statements, 10,000 DML rows, 50,000 records retrieved by SOQL, 6 MB of heap on synchronous code, 10 seconds of CPU time on synchronous code (60 seconds on async). The counters start at zero when the transaction begins and reset to zero when it ends. Code that chains transactions (a trigger that enqueues a Queueable that enqueues another Queueable) gets a fresh quota for each chained job, which is why developers split work that would otherwise hit a limit.

Atomic commit and automatic rollback

DML changes inside a transaction are not visible to other users until the transaction commits. If any uncaught exception escapes the top-level code, the platform rolls back every insert, update, delete, and undelete that ran in the transaction. There is no halfway commit. This is why developers use try/catch sparingly at the wrong level: a try/catch that swallows an exception inside a trigger can leave partial DML committed when the calling code expected a clean rollback. The rule is to let exceptions propagate unless the code can genuinely recover.

Savepoints and partial rollback

Apex supports a Savepoint type. The developer calls Database.setSavepoint() at a known good state, runs some risky DML, and rolls back to the savepoint if anything fails (Database.rollback(sp)). Subsequent DML still happens, the SOQL counter persists, and the transaction continues. Savepoints are the only way to do partial rollback in Apex, and they are critical for compensating logic (try a primary write, fall back to a secondary write if it fails). The SOQL query counter does not reset on rollback, which is the most common surprise developers encounter.

Static variables and transaction state

Static variables live for the lifetime of one Apex transaction. They are initialized when the class is first referenced and discarded when the transaction ends. Triggers commonly use static booleans to prevent recursive runs (Trigger.isExecuting) and static maps to cache lookup data across multiple trigger calls inside the same transaction. The state does not carry across transactions; a future job called from a trigger sees a fresh static variable. This is the source of two opposite bugs: code that relies on static state surviving across transactions (it does not), and code that forgets to reset a static between deliberately reused transactions in tests.

Synchronous vs asynchronous transactions

Synchronous Apex (triggers, controllers, anonymous Apex) runs in the user-facing transaction and has the strictest governor limits. Asynchronous Apex (future, Queueable, Batch, Scheduled) runs in its own transaction with relaxed limits: 60-second CPU, larger SOQL row counts, more callouts. The trade-off is that async code cannot return a result to the caller in the same transaction; the caller must poll or wait for a follow-up event. Designing a feature is largely a question of which work fits in the user-facing transaction and which work needs to be queued.

Order of execution and trigger interactions

Salesforce documents a strict order of execution within a single transaction: before triggers, validation rules, after triggers, assignment rules, auto-response rules, workflow field updates, post-commit triggers, escalation rules, flow processes, summary field recalculations, and roll-up summary recalculations. Each step can fire DML that re-enters the transaction at a recursive level. Knowing the order is what separates someone who can debug a trigger bug from someone who cannot, because the order determines which field value a downstream rule sees.

§ 03

Use Savepoints to add partial rollback to Apex

Add a Savepoint to your Apex transaction so you can attempt a risky DML and roll back to a known good state if it fails, without aborting the entire transaction.

  1. Identify the rollback boundary

    Pick the point in your code where the state is consistent and you would be willing to discard subsequent work if needed. This is usually right before a multi-step DML operation that might partially fail.

  2. Create the savepoint

    Call Savepoint sp = Database.setSavepoint(); at the rollback boundary. The handle holds the transaction state at that point.

  3. Wrap the risky DML in try/catch

    Try the DML operations you want to compensate. Catch the DmlException (or specific exception type). Inside the catch, call Database.rollback(sp); to revert to the savepoint state.

  4. Run compensating logic

    After the rollback, optionally execute an alternative path: log the failure, insert a fallback record, or notify a queue. The transaction continues from the savepoint, not from the start.

  5. Test the partial rollback in apex tests

    Write a unit test that forces the risky DML to fail (a validation rule, a unique constraint, a missing required field). Verify the records before the savepoint are still present and the compensating logic ran.

  6. Mind the SOQL counter

    Remember that Database.rollback() does not reset the SOQL query counter. If your savepoint code ran many queries, you can still hit the per-transaction limit even after rolling back.

Savepointremember

Handle returned by Database.setSavepoint(); marks a known good state to roll back to.

allOrNone flagremember

Parameter on Database.insert/update/delete that controls whether one bad row rolls back the whole batch. Independent from Savepoints.

Async transactionremember

Queueable, Future, Batch, Scheduled. Each runs in its own transaction with its own savepoint scope.

Static variableremember

Class-level field that lives for one transaction. Useful for caching and recursion guards inside the transaction.

Gotchas
  • Database.rollback(sp) does not reset the SOQL or DML statement counter. You can roll back and still exceed the transaction limit on retry.
  • Uncaught exceptions roll back the entire transaction, even DML performed before a savepoint. Use try/catch deliberately at the boundary that needs to handle the failure.
  • Future, Queueable, and Batch jobs queue from a transaction but execute in their own transactions later. Limits on the calling transaction do not transfer; limits on the callee transaction start fresh.
  • Trigger order of execution causes recursive saves to look like the same transaction even though logically they are nested calls. Use a static boolean guard to prevent infinite recursion.
§

Trust & references

Sources

Cross-checked against the following references.

Official documentation

Straight from the source - Salesforce's reference material on Transaction, Apex.

Keep learning

Hands-on resources to go deeper on Transaction, Apex.

Was this entry helpful?
Help us write better definitions. Quick reactions or detailed edit suggestions.

About the Author

Dipojjal Chakrabarti is a B2C Solution Architect with 29 Salesforce certifications and over 13 years in the Salesforce ecosystem. He runs salesforcedictionary.com to help admins, developers, architects, and cert/interview candidates sharpen their fundamentals. More about Dipojjal.

§

Test your knowledge

Q1. What is an Apex Transaction?

Q2. What's atomic about transactions?

Q3. Where are governor limits enforced?

§

Discussion

Loading…

Loading discussion…