[readonly] markdown buffer
Where Flow Should End and Apex Should Begin
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.
- 01Case createdstart
- 02Priority is critical?decision
- 03Request approvalaction
- 04Notify support leadaction
- 05Update statusend
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.
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.