The Short Answer
The question "Flow or Apex" receives an incorrect answer when evaluated based on ease of writing or developer availability. The correct answer depends on four technical factors: how many records are processed in a single transaction, whether the logic requires full atomicity, how complex the conditions and branches are, and who will maintain the component in a year. Flow is the right default for most business automations—but there are clear transition points where continuing to use Flow creates an operational risk, not just "less elegant code."
This article focuses on the decision-making process itself: how to identify in advance that the logic justifies Apex, and how to prevent situations where the choice is made by default rather than by deliberate consideration. The issue of cleaning up existing Flows and automation processes that have accumulated as technical debt is discussed in a separate article and is not part of this discussion.
What Actually Differentiates Them at the Platform Level
Flow is a declarative engine that is translated at runtime into instructions that execute DML and SOQL on behalf of the user, whereas Apex is compiled code that runs under the same Governor Limits but with direct control over the order of operations. The first practical difference is bulkification: an Apex developer explicitly builds a loop that collects all records into a single array and runs a single DML, while in Flow, it's easy to build a loop that performs a DML operation or a query in each iteration separately—a pattern that reaches the 101 allowed queries much faster.
The second difference is transaction control. Apex allows Savepoint and Database.rollback for partial undo, handling DmlException at the single record level via Database.insert(list, false), and complex conditional logic without branch depth limits. In Flow, error handling is defined at the Fault Path level for each element, and this works well for linear scenarios but becomes difficult to track with more than a few parallel fault paths.
Decision Framework: Four Tests Before Choosing a Tool
Volume Test
Rule of thumb: If the process runs on a single record as a result of user action (creating a lead, changing opportunity status), Flow is almost always sufficient. If the process runs on tens to thousands of records at once—periodic updates, handling a batch from an integration, scheduled data cleanup—Apex with Batchable or Queueable is the safe choice, as it provides full control over bulkification and managing Governor Limits against varying volumes.
Atomicity Test
Ask: If part of the update fails, is it acceptable for the other part to remain saved? If the answer is "no"—for example, an order update and the creation of a billing record that must happen together—Apex with Savepoint is the correct way to ensure this. Flow does not provide full rollback between elements without complex manual construction of compensation logic.
Branch Complexity Test
A Flow with more than 6-8 nested Decision Elements becomes hard to read and expensive to test, even if each individual branch is simple. When the complexity of the business logic exceeds this, writing the same logic as a documented Apex function with unit tests (@isTest) is usually cheaper to maintain, even if the initial writing time is longer.
Maintenance and Ownership Test
Ask who will maintain the component in a year, not who is building it now. If the Admin team is the one that will need to update business rules regularly—such as changing discount conditions or threshold values—Flow is preferable even if Apex is technically "cleaner," because it is accessible for updates without a deployment cycle. If the changes require knowledge of the data schema and regression testing, Apex is the correct choice even if there is only a small development team to maintain it.
Decision Table
| Criterion | Choose Flow | Choose Apex |
|---|---|---|
| Record Volume in a Single Transaction | Up to a few dozen | Hundreds to thousands |
| Atomicity Requirement Between Multiple Objects | Not critical | Critical – full Rollback required |
| Number of Decision Branches | Up to ~6-8 | Above this, or recursive logic |
| Rate of Business Rule Changes | Frequent, by Admin | Rare, requires regression testing |
| Need to Call Complex External API | Single simple call (HTTP Callout) | Retry logic, complex Auth, or Batch |
| Requirement for Automated Testing (CI) | Limited | Full, @isTest with Coverage |
| Integration with Scheduled Job | Not directly suitable | Native via Schedulable |
Example Scenario: Medical Device Company with Order Approval Process
A medium-sized medical device company with about 40 sales representatives used a single Flow for its order approval process: checking inventory, calculating discounts, creating an approval record, and sending an alert to the manager. Initially, it worked well for a single order. After six months, a new scenario was added—batch order import from a file integrated with the ERP, creating between 200 and 800 orders simultaneously.
The Flow, which was triggered by a Record-Triggered Flow at the "per record" level, performed an inventory check query within each individual execution. With an import of 500 orders, the system exceeded the 100-query limit in a single transaction, and orders failed without a clear error message to the user. The team identified that the problem was not with the Flow itself, but with the mismatch between a process designed for a single record and a high-volume scenario that did not exist at the time of build.
The solution was not to discard the Flow. The team split the logic: Flow remained responsible for the manual process of a single order (low volume test, frequent updates of discount rules by Admin needed), while the batch import process was moved to an Apex Batch Job that performs full bulkification, checks inventory in one centralized query, and runs a single DML for all records. Both mechanisms call the same shared business logic layer (a single Apex Class that the Flow also calls via an Invocable Method), so that the discount rule is not maintained twice.
Common Risks and Preventive Actions
| Risk | What it Looks Like in Practice | Preventive Action |
|---|---|---|
| Flow on gradually increasing volume | Process worked for six months, then failed silently on Governor Limits | Assess expected volume in advance and plan a transition point to Apex before reaching the limit |
| Duplicate business logic in Flow and Apex | Two places calculate discounts differently | Centralize business calculations in a shared Apex layer that Flow also calls |
| Unexpected Trigger Order | Several Flows and Triggers on the same object conflict | Use one central Trigger Handler in Apex for every critical object |
| Partial error handling in complex Flow | Some records are updated and some are not, without visibility | Move processes requiring Atomicity to Apex with Savepoint |
| Apex without sufficient tests | A small change breaks a critical process in the next deployment | Demand true Coverage, not just a formal percentage, including failure scenarios |
Pre-Build Decision Checklist
- ☐ Expected volume for a year, not just current state, has been assessed
- ☐ It has been determined if the process requires atomicity between several objects
- ☐ The expected decision branches in the logic have been counted
- ☐ It is known who will maintain the component and how often rules will change
- ☐ It has been checked if similar logic already exists in Apex or another Flow on the same object
- ☐ Trigger Order has been defined if there are multiple automation mechanisms on the object
- ☐ If Apex is chosen—test scenarios, including partial failure, have been defined
- ☐ If Flow is chosen—a Fault Path has been defined for every critical element
How This Connects to the Broader Architecture
Choosing the right tool for a single automation is just one layer within a broader picture of CRM Architecture, where both data models and permissions affect what Flow or Apex can even touch. When automation crosses into an external organization—for example, real-time inventory checks against an ERP—the choice between Flow and Apex also integrates into considerations of Integration Patterns and how Salesforce Connects to ERP in terms of latency and failure handling.
In organizations operating multiple Orgs, it is also necessary to check if the business logic is identical across all of them—a topic discussed in the Single Org vs. Multi Org Guide and impacting whether it is worthwhile to centralize the logic in a shared Apex package.
Summary
The choice between Flow and Apex is not a question of team skill or personal preference, but rather a result of four technical tests: volume, atomicity, branch complexity, and frequency of change. Flow is the correct default for most automations dealing with a single record and changing frequently. Apex is required when there's significant volume, when full transaction control is needed, or when logical complexity exceeds the threshold that can still be maintained through a declarative interface. An organization that institutionalizes these tests as part of its workflow—rather than leaving them to ad-hoc developer discretion—will avoid most cases where an automation that initially worked well silently breaks as volume grows.
