Log Entry

Where Flow Should End and Apex Should Begin

Jun 4, 2026 · 12 min read

The Flow versus Apex argument is usually framed badly.

Flow is not the beginner option.

Apex is not the grown-up option.

They are two implementation tools with overlapping capabilities and very different maintenance shapes.

The question I ask is:

Where can the business process stay visible, and where does the implementation need programmatic control?

Often the answer is both.

Quick snapshot

I keep work in Flow when it is:

  • a visible sequence of business steps
  • low to moderate in data volume and complexity
  • likely to be changed by an admin
  • well served by standard Flow elements
  • easier to support when shown in Flow Builder

I move a unit of work to Apex when it needs:

  • maps, sets, or non-trivial collection processing
  • precise transaction or partial-success control
  • high-volume performance
  • reusable domain logic outside one Flow
  • complex validation with focused automated tests
  • an external integration that needs a real client boundary

Salesforce's record-triggered automation decision guide describes this in terms of automation density: Flow for lower-density automation, Flow with invocable Apex as complexity rises, and Apex triggers for high-density automation.

That is much more useful than choosing by ideology.

Flow is excellent at orchestration

Flow Builder is good at showing a process:

  1. gather or receive input
  2. make a decision
  3. request approval
  4. update records
  5. notify somebody

A support lead can open that Flow and understand the broad path without reading a class hierarchy.

That visibility has real value. It makes business logic discussable, reviewable, and often quicker to change.

I do not replace a clear five-element Flow with Apex merely because I can write the Apex quickly.

Code is not free. It moves ownership towards developers, deployments, tests, and code review.

The first warning sign: collection logic becomes the diagram

Flow can process collections, but not every collection problem belongs in boxes and connectors.

The warning signs are familiar:

  • nested loops
  • repeated collection scans
  • parallel collections whose indexes must stay aligned
  • elaborate text keys standing in for a map
  • assignment elements that exist only to simulate a data structure

At that point, the diagram no longer explains the business process. It explains an algorithm awkwardly.

That algorithm is usually smaller and clearer in a focused Apex action.

Map<Id, Decimal> totalByAccountId = new Map<Id, Decimal>();

for (Opportunity opportunity : opportunities) {
    Decimal currentTotal = totalByAccountId.get(opportunity.AccountId);
    totalByAccountId.put(
        opportunity.AccountId,
        (currentTotal == null ? 0 : currentTotal) + opportunity.Amount
    );
}

The Flow can still own when the calculation happens and what happens next.

The second warning sign: the transaction matters

Flow handles many record operations very well, but some requirements are explicitly transactional:

  • roll everything back if a later step fails
  • use a savepoint around a risky operation
  • accept valid records while returning errors for invalid records
  • control DML ordering to avoid lock contention
  • report an outcome for every submitted item

Those are Apex-shaped requirements.

If the acceptance criteria use phrases such as "all or nothing", "partial success", "retry safely", or "exactly once", I stop treating the transaction as an implementation detail.

The third warning sign: bulk behaviour is hard to prove

Flow has bulkification behaviour, but it cannot make an inefficient design cheap.

A record-triggered automation might behave perfectly when a user edits one record and fail when an integration updates 200.

I move towards Apex when I need explicit control over:

  • query count
  • DML count
  • collection cardinality
  • deduplication within the transaction
  • expensive calculations shared across records

The important word is explicit.

For high-volume paths, I want a test that inserts or updates a realistic collection and asserts both the outcome and the limit-sensitive design.

The fourth warning sign: logic has more than one caller

Business rules should not be trapped inside the first surface that needed them.

If "calculate entitlement" is required by a Flow today, an LWC tomorrow, and a batch repair next month, it belongs in a reusable service.

public class EntitlementService {
    public static List<EntitlementDecision> evaluate(
        List<EntitlementRequest> requests
    ) {
        // Reusable business logic.
    }
}

An invocable class can adapt Flow inputs to that service. An LWC controller can adapt a UI request to the same service.

The service should not know which UI or automation happened to call it.

The hybrid shape I like

My preferred hybrid design is:

  • Flow owns orchestration and business-visible decisions.
  • Invocable Apex owns one cohesive unit of difficult work.
  • A service class owns reusable domain logic.
  • The action returns a structured result that Flow can reason about.

The invocable action should not become "run the whole application".

If the Flow consists of Start -> Giant Apex Action -> End, the orchestration is not really in Flow. That might still be correct, but it should be an intentional Apex design rather than a decorative Flow wrapper.

One entry point matters

The most difficult orgs to reason about are not Flow orgs or Apex orgs.

They are orgs where the same object has several record-triggered Flows, multiple Apex triggers, managed-package automation, and undocumented recursion between all of them.

For a given object, I want one governed entry strategy and a known order of execution.

That does not mean every requirement lives in one mega-Flow or one giant trigger. It means the entry points are deliberate and the downstream units are composed predictably.

A practical decision checklist

Before building, I ask:

  1. Can a non-developer understand and safely change this process?
  2. Does the logic need maps, sets, recursion, or complex collection transforms?
  3. Is there a strict rollback or partial-success requirement?
  4. What happens when 200 records enter the transaction?
  5. Will another caller need the same business operation?
  6. Which team will own failures at 02:00?

If the answers point in different directions, use a hybrid.

Flow and Apex are not competing for the trophy.

They are allowed to have boundaries.