- Why Salesforce shifted to a "Flow-first" guidance in 2021 and what it means in practice
- The capability gap between Flow and Apex — what each can and cannot do
- The decision framework: seven criteria for choosing between Flow and Apex
- Testing Flow vs testing Apex and why the testing gap is significant for quality
- The hybrid pattern: Flow orchestration with Apex invocable actions
- Technical debt from poor Flow vs Apex decisions and how to identify it
The Salesforce "Flow-First" Guidance and What It Actually Means
In 2021, Salesforce deprecated Workflow Rules and Process Builder and announced that Flow (specifically Record-Triggered Flow) is the strategic successor for declarative automation. Combined with ongoing investments in Flow capability, this led to the "Flow-first" guidance: where Flow can solve the problem, prefer Flow over Apex.
This guidance has been widely interpreted but frequently misapplied. "Flow-first" does not mean "Flow always." It means: before writing Apex, verify that the requirement cannot be met with Flow. If Flow can meet the requirement equally well, choose Flow for its lower maintenance overhead, lower barrier to admin maintenance, and alignment with Salesforce's investment direction.
The guidance also does not mean that all existing Apex should be converted to Flow. A well-tested, well-structured Apex trigger that works correctly is not a problem. The guidance applies to new development decisions, not to retroactive refactoring of functioning code.
What Flow Can Do — and Where It Falls Short
Record-Triggered Flows have reached near-parity with Apex triggers for the most common automation scenarios. Understanding the current capability boundary prevents both over-use and under-use of Flow.
Flow excels at: Updating fields on the triggering record and related records, creating related records, sending emails and notifications, posting to Chatter, calling external REST APIs (via HTTP callout actions), evaluating simple conditions and branching logic, orchestrating multi-step approval processes, and running scheduled automation at configured intervals.
Flow cannot do: Complex collection manipulation (sorting, deduplication, complex aggregation on in-memory collections), recursive logic (Flows cannot call themselves), generating and parsing complex data structures (custom XML, binary data, encrypted payloads), complex error handling with typed exceptions, certain callout patterns requiring stateful session management, and extremely high-volume bulk processing where Apex Batch patterns are required.
Flow's hidden complexity tax: As a Flow grows beyond ~20-30 elements, it becomes visually overwhelming and difficult to read. Nested decision branches in Flow are harder to trace than equivalent if-else in Apex. Complex Flows often contain logic that is invisible at a glance — a formula field in a Loop element that manipulates a collection variable in a non-obvious way. This "complexity tax" is the primary reason complex logic belongs in Apex.
The Decision Framework: Seven Criteria
When facing a new automation requirement, evaluate it across these seven criteria to determine the right tool.
1. Complexity of logic: If the logic requires sorting, deduplication, complex string manipulation, or recursive patterns, use Apex. For simple conditional field updates and related record creation, use Flow.
2. Data volume: If the automation will process large numbers of records in batch (thousands to millions), use Apex Batch or Queueable. Flow is not designed for bulk data processing at scale and will hit governor limits with very large batch operations.
3. Error handling requirements: If the failure mode requires specific error recovery, typed exception handling, or partial success patterns, use Apex. Flow's error handling is limited — faults in Flow either fail the transaction or route to a fault connector that can take a limited set of actions.
4. Testability requirements: If the business logic is critical (financial calculations, compliance-relevant decisions) and requires comprehensive, deterministic test coverage with assertions, use Apex. Apex test classes provide programmatic assertions; Flow tests are less capable for precise assertion of complex outcomes.
5. Team maintenance profile: If the primary maintainers of this automation are Salesforce admins without Apex development skills, Flow is more appropriate even for moderately complex logic. Apex that only a developer can modify is a bottleneck if the organisation's ongoing maintenance team is admin-centric.
6. Integration with other automation: If the logic needs to call and receive data from other Apex classes or be invoked programmatically from code, Apex is more appropriate. Flow is callable from Apex (via InvocableMethod), but tightly coupled orchestration between Flow and Apex fragments the logic.
7. Performance requirements: If the automation runs on a high-frequency object (Orders being created at 1,000/minute during peak loads) and performance has been profiled, validate that Flow's overhead meets the performance requirement. Record-Triggered Flows add measurable transaction overhead — for extremely high-frequency automation, Apex triggers may perform better.
// Apex Invocable Method — called from Flow as an action
public class CreditCheckService {
@InvocableMethod(label='Check Credit Limit'
description='Validates order amount against account credit')
public static List<Result> checkCredit(List<Request> requests) {
List<Result> results = new List<Result>();
for (Request req : requests) {
Result r = new Result();
// Complex credit check logic here — too complex for Flow
// Queries ERP integration, evaluates tiered credit rules, etc.
r.isApproved = evaluateCreditRules(req.accountId, req.orderAmount);
r.creditLimit = getCreditLimit(req.accountId);
results.add(r);
}
return results;
}
public class Request {
@InvocableVariable public Id accountId;
@InvocableVariable public Decimal orderAmount;
}
public class Result {
@InvocableVariable public Boolean isApproved;
@InvocableVariable public Decimal creditLimit;
}
}
Testing: The Quality Gap
The testing asymmetry between Flow and Apex is one of the most important architectural considerations and is frequently underweighted in the Flow vs Apex decision.
Apex requires 75% code coverage to deploy. More importantly, Apex test classes enable fine-grained assertions: verify that specific field values were set, that specific exceptions were thrown, that specific methods were called. This enables test-driven development and regression testing that can catch specific logic errors before they reach users.
Flow testing (via the Flow Test capability) is improving but remains less mature. Flow tests can assert the values of output variables and changed record fields, but they cannot easily assert intermediate state, cannot test specific branch paths in isolation, and are limited in their ability to test error handling paths. For complex business logic, the inability to comprehensively test Flow is a real quality risk.
The Hybrid Pattern: Flow Orchestrates, Apex Handles Complexity
The most architecturally sound pattern for complex Salesforce automation is not "all Flow" or "all Apex" — it is a hybrid where Flow handles the orchestration (what triggers, what sequence, what conditional branches) and Apex handles the complex logic (via InvocableMethod actions called from Flow).
This hybrid pattern provides several advantages. The business process flow is visible as a Flow diagram — non-technical stakeholders can review the process structure. The complex business logic is isolated in Apex with full test coverage and type safety. Admins can modify the orchestration (change when a step runs, add a new branch) without touching the Apex. Developers own the Apex methods, which have clear, testable interfaces.
The boundary between "what goes in Flow" and "what goes in the Apex InvocableMethod" follows a simple rule: Flow handles decisions about process structure (if this condition, go this path); Apex handles decisions about data (calculate this value, validate this business rule, call this external system). This separation of concerns keeps each layer clean and maintainable.
Key Takeaways
- "Flow-first" means verify that Flow can solve the problem before writing Apex — it does not mean convert all Apex to Flow or always use Flow regardless of complexity.
- Flow excels at orchestration, simple conditional automation, and admin-maintainable processes. It falls short on complex collection manipulation, recursive logic, typed error handling, and high-volume bulk processing.
- The seven decision criteria (complexity, data volume, error handling, testability, team skills, integration, performance) provide a structured framework for making the Flow vs Apex choice per requirement.
- The testing quality gap is significant: Apex enables precise assertion-based testing with 75% coverage enforcement; Flow testing is less mature and cannot comprehensively test complex logic paths. Business-critical logic belongs in Apex for this reason.
- The hybrid pattern — Flow orchestrates, Apex InvocableMethod actions handle complex logic — is often the architecturally superior approach, combining the process visibility of Flow with the quality and testability of Apex.
- Complex Flows (30+ elements, nested loops, multiple collection variables) should trigger a reassessment — at this complexity level, Apex is often more maintainable even if Flow is technically capable.
Test Your Understanding
1. A Record-Triggered Flow grows to 45 elements with nested loops that manipulate three collection variables to calculate a complex discount. A developer argues this should be refactored to Apex. Which is the most sound argument for the refactor?
2. A Salesforce admin team (no Apex skills) needs to build an automation for opportunity approval routing. The logic involves 4 conditions, email notifications, and creating a Task record. What is the right tool choice?
3. In the hybrid Flow/Apex pattern, where should complex financial calculations (revenue recognition rules, FX currency conversions) be implemented?
Discussion & Feedback