💃 Apex Transactions Explained 🕺

Salesforce’s execution truth

Good morning, Salesforce Nerds! An Apex transaction is Salesforce’s fundamental unit of work.

Every meaningful data change happens inside one. 📥️ 

When a transaction starts, Salesforce creates an execution bubble and invites every relevant participant inside: triggers, record-triggered Flows, Apex, validation rules, workflow actions, and database operations.

A transaction begins whenever Salesforce needs to persist data.

That includes UI saves, API calls, record-triggered Flows, triggers firing, or Apex invoked by automation. 👈️ 

Once started, Salesforce tracks every field change and decision until it either commits or rolls everything back.

Here’s the architectural reframe that matters: Flow and Apex are peers, not layers stacked on top of each other. 💯 

They execute within the same transactional boundary.

If one fails, they all fail. If one commits, they all commit.  

That single idea explains more “weird Salesforce behavior” than any debugging trick ever will.

TABLE OF CONTENTS

COMMIT OR VANISH

ALL OR NOTHING

Salesforce transactions are atomic. They follow a strict all-or-nothing rule. 📐 

If an unhandled exception escapes execution, Salesforce will:

  • Rolls back every DML operation

  • Discards all field changes

  • Leaves the database exactly as it was

Handled exceptions behave differently.

If you catch an exception and continue, Salesforce assumes you know what you’re doing and keeps the transaction alive. 😅 

Savepoint sp = Database.setSavepoint();

try {
    insert records;
    riskyOperation();
} catch (Exception e) {
    Database.rollback(sp);
}

This distinction is subtle and critical.

Many teams assume “an error occurred” equals “rollback happened.” 🤔 

But, that’s only true if the exception is not handled.

Savepoints are your escape hatch when you want controlled failure without detonating the entire transaction. 💣️ 

WHO RUNS WHEN

THE EXECUTION PARADE

Salesforce executes automation in a precise and repeatable order. 🔄 

Highlights, simplified:

  1. Load record values

  2. Before-save record-triggered Flows

  3. Before triggers

  4. Validation rules

  5. After-save record-triggered Flows

  6. After triggers

  7. Workflow, processes, Flow actions

  8. Commit

A few architectural consequences follow: 🏗️ 

  • Before-save Flows run before before-triggers

  • After-save Flows run after triggers

  • Autolaunched Flows invoked from Apex stay inside the same transaction

  • Everything above happens before commit

This is why moving logic between Flow and Apex can change outcomes even when the logic is identical. 🧠 

Timing matters as much as code.

SCHRODINGER MEETS TRAFFIC

ISOLATION AND CONCURRENCY

Inside a transaction, Salesforce supports read-your-own-writes. ✍️ 

Insert a record, and you can immediately query it again.

insert acct;

Account a = [SELECT Id FROM Account WHERE Id = :acct.Id];

That record exists, but only inside this specific transaction. ‼️ 

Outside the transaction, it does not. Until commit. 🤯 

This means:

  • Other users cannot see it

  • Other transactions cannot query it

  • Other automations cannot depend on it

Salesforce behaves like a READ COMMITTED database.

No dirty reads. No half-finished data. ⛔️ This isolation protects consistency at massive scale.

Now, add concurrency. 😰 

Salesforce does not provide implicit record locking for business logic.

Two transactions can:

  • Query the same “next available” record

  • Both believe they’re first

  • Both attempt updates

Congratulations, you’ve discovered a race condition. 🏁 

Architects must design for this explicitly:

  • Use FOR UPDATE when appropriate

  • Introduce queue or semaphore objects

  • Design idempotent logic

  • Avoid “SELECT then INSERT” assumptions

Salesforce optimizes for throughput, not serialized execution. Safety is a design responsibility. 🦺 

NEW TRANSACTION, NEW RULES

ASYNC MEETS NEW REALITY

Asynchronous Apex always runs in a separate transaction:  

  • @future

  • Queueable

  • Batch (each execute chunk)

  • Scheduled Apex

  • Platform Event and CDC-triggered Flows

These execute after the original transaction commits.

They do not share: 🙅 

  • Savepoints

  • Rollback behavior

  • Pending data visibility

This separation is intentional. 👈️ 

It gives you clean commit boundaries, retry safety, and eventual consistency.

It also means assumptions from synchronous logic no longer apply.  

Every async entry point is a fresh execution universe.

ARCHITECTURE, NOT HOPE

DESIGNING WITH INTENT

Once you internalize Salesforce’s transaction model, good architecture becomes easier:

  • Treat Flow and Apex as co-equal participants

  • Never assume visibility outside your transaction

  • Use savepoints deliberately

  • Design for concurrency, not luck

  • Push long-running work async

  • Prefer explicit coordination patterns over timing assumptions

Salesforce isn’t unpredictable. It’s consistent. 🔥 

Teams struggle when they design as if transactions are suggestions instead of contracts.

Master transactions, and your automations stop being fragile. 💪 

They become deliberate, scalable, and boring in the best possible way.

SOUL FOOD

Today’s Principle

"Transactions are the basic unit of correctness in a system."

Jim Gray

and now....Salesforce Memes

What did you think about today's newsletter?

Login or Subscribe to participate in polls.