XSCREENSAVER / 3D PIPES

[readonly] markdown buffer

Where Flow Should End and Apex Should Begin

Jun 4, 2026 · 6 min read

The Flow versus Apex argument is usually framed badly.

Flow is not the beginner option, and Apex is not the grown-up option. They are tools with overlapping capabilities and different maintenance shapes.

The useful question is: where should the business process remain visible, and where does the implementation need programmatic control?

Often the answer is both.

Keep orchestration visible

Flow is good at showing a process: receive input, make a decision, request approval, update records, and notify somebody.

That visibility matters. A support lead can inspect the path without reading a class hierarchy. I do not replace a clear five-element Flow with Apex merely because I can write the code quickly.

Salesforce's record-triggered automation guide frames the choice by automation density: Flow at lower density, Flow with invocable Apex as complexity rises, and Apex triggers for dense automation.

traceview://flow-visible
record-triggered flowthe business process is the diagram
  1. 01Case createdstart
  2. 02Priority is critical?decision
  3. 03Request approvalaction
  4. 04Notify support leadaction
  5. 05Update statusend
A support lead can inspect when, why and what happens next without opening Apex.
A short Flow earns its visibility: the diagram directly shows the trigger, decision, approval, notification and final update.

Move the algorithm, not the whole process

The boundary appears when the diagram stops explaining a business process and starts simulating a programming language.

Nested loops, repeated collection scans, parallel lists, and text keys standing in for maps are signs that a focused Apex action would be clearer.

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

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

Flow can still own when this calculation happens and what follows it.

Transaction requirements create another boundary. All-or-nothing changes, partial success, retries, lock-sensitive DML ordering, and one outcome per input are Apex-shaped concerns.

Scale matters too. When 200 records enter the transaction, I want explicit control over query count, DML count, deduplication, and shared calculations.

Put reusable rules below both surfaces

Business logic should not be trapped in the first caller that needed it.

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

An invocable action can adapt Flow inputs to this service. An LWC controller or repair job can use the same operation later.

The hybrid shape I prefer is simple: Flow owns visible orchestration, invocable Apex owns one cohesive difficult unit, and a service owns reusable domain logic. If the Flow is Start → Giant Apex Action → End, the orchestration is not really in Flow.

traceview://flow-apex-boundary
algorithm drawn as Flowthe process disappears
StartLoopFound? LoopAssignDuplicate? AssignUpdate
visible orchestrationone difficult unit
01receive requestFlow
02check approvalFlow
03calculate allocationsinvocable Apex
04notify ownerFlow
When loops and collection mechanics consume the Flow canvas, move that algorithm into one focused Apex action and leave the business process visible around it.

Choose the boundary deliberately

Before building, ask whether an admin should understand and change the process, whether the logic needs real data structures, whether rollback or partial success matters, what happens at bulk size, and whether another caller will need the same operation.

Also decide who owns failures at 02:00.

If the answers point in different directions, use a hybrid. Flow and Apex are not competing for a trophy. They are allowed to have boundaries.