💃 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.