The quick version: something rejected the save on purpose — a validation rule, a flow, or Apex calling addError(). The message you see is text an admin typed, not platform wording, so search your org for that exact string. Start at Setup → Object Manager → your object → Validation Rules.
The message is the fingerprint
Every other DML error gives you platform-authored wording. This one gives you an admin’s wording. That is the fastest route to the culprit: the string after the status code was typed into an errorMessage field, a flow’s custom error, or an addError() call.
A raw one from a load:
FIELD_CUSTOM_VALIDATION_EXCEPTION: Close Date cannot be in the
past for a Stage of Closed Won: [CloseDate]
The bracketed field at the end is the rule’s errorDisplayField, which the Metadata API describes as “the fully specified name of a field in the application. If a value is supplied, the error message appears next to the specified field.” No brackets means the rule displays at the top of the page instead.
Ignore the documented definition of the status code itself — the SOAP API reference gives it as “You can’t define a custom validation formula that violates a field integrity rule,” which describes a different situation entirely. Work from the message text.
Three things throw this, and you can tell them apart
All three produce the same status code, which is why people waste time hunting only through Validation Rules.
| Source | How to recognize it | Where to look |
|---|---|---|
| Validation rule | Static message, identical every time, often ends with a field name in brackets | Object Manager → Validation Rules |
Apex addError() |
Message often has record data, Ids, or counts stitched in at runtime | Triggers on the object and their handler classes |
| Flow custom error | Sentence-style wording; a flow error email may arrive at the same moment | Setup → Flows, filtered to the object |
The Apex reference is explicit about addError(): it “marks a trigger record with a custom error message and prevents any DML operation from occurring.” At the API boundary that is indistinguishable from a validation rule. Only the message text separates them.
Find the rule in about thirty seconds
The click path works but is slow in an org with 60 rules across 12 objects. Query the Tooling API instead — ValidationRule has been queryable there since API version 34.0, exposing Active, ErrorMessage, ErrorDisplayField, EntityDefinitionId, and ValidationName.
sf data query --use-tooling-api \
--query "SELECT EntityDefinitionId, ValidationName, Active, ErrorDisplayField, ErrorMessage FROM ValidationRule WHERE ErrorMessage LIKE '%Close Date cannot be%'"
That returns the object and rule name directly. Drop the WHERE clause and dump every rule to a file the first time you touch an unfamiliar org.
If the message is not in that result set, it came from Apex or a flow. Grep your repo for a distinctive fragment. With no local source, retrieve first: sf project retrieve start --metadata ApexTrigger ApexClass Flow, then search.
Confirm it in a debug log
Setup → Debug Logs → New, add the user, reproduce the save. Validation rules appear as their own code units — the Apex Developer Guide lists VALIDATION_RULE among the debug log event types, and the log shows the rule being entered, the formula evaluated, and the outcome.
The default SFDX_Developer log level does not raise the Validation category high enough. Create a custom level with Validation at INFO or DEBUG or you will get triggers and no rules.
One detail that matters while reading: the order of execution runs before-save flows and before triggers ahead of system and custom validation. So a before-save flow that rewrote a field can make a rule fail on a value the user never entered. Check what the record looked like at validation time, not what was submitted.
Causes, most frequent first
An API load hitting a rule written for the UI. This is the most common version by a wide margin. Validation rules fire on API inserts and updates exactly as on page saves: Data Loader, Bulk API, REST, integrations, all of them. A rule assuming a human fills fields in a particular order breaks the moment a load supplies them differently or omits one.
A field the running user cannot see, referenced in the formula. Validation formulas evaluate field values regardless of field-level security, so a rule can reject a save for a reason invisible on the page in front of the user. Fix the message text, not the formula.
Cross-object references going null. A rule on Opportunity referencing Account.Industry fails unpredictably when there is no Account. Guard those with ISBLANK() or NOT(ISBLANK(AccountId)).
A managed package rule. If the Tooling API query shows a namespace prefix, you cannot edit it. Look for a bypass setting the package ships, or open a vendor case.
In bulk, stop letting one bad record kill the batch
A bare insert throws on the first failure and rolls everything back. Pass false as allOrNone to a Database method and, per the Apex Developer Guide, “if a record fails, the remainder of the DML operation can still succeed. Your application can then inspect the rejected records and possibly retry the operation.”
List<Database.SaveResult> results = Database.insert(opportunities, false);
for (Integer i = 0; i < results.size(); i++) {
if (results[i].isSuccess()) {
continue;
}
for (Database.Error err : results[i].getErrors()) {
System.debug(LoggingLevel.ERROR,
opportunities[i].Name
+ ' | ' + err.getStatusCode()
+ ' | ' + err.getMessage()
+ ' | fields: ' + String.join(err.getFields(), ','));
}
}
getFields() is the part people skip, and it is what turns a vague message into an actionable one. It names the field the error was attached to without parsing brackets out of a string. getStatusCode() lets you branch: retry on a lock error, log and skip on a validation error.
The tradeoff is real. With allOrNone false you have accepted a partially applied batch, so the job must record what failed and be safe to re-run. If your process cannot tolerate a half-finished load, keep all-or-none and fix the data upstream.
Validation rules or before-save automation? I use rules, with limits
You can enforce the same constraint two ways: a rule that rejects the save, or a before-save flow that quietly corrects the value. My default is the rule, because it is discoverable: it shows up in the Tooling API query above, in a metadata retrieve, and in the error message itself. Silent correction in a flow shows up nowhere and produces the worst class of ticket: “the value I typed changed by itself.”
Where I flip is inbound integration data. If a partner sends “USA” and you need “United States”, correcting it in a before-save flow is right and rejecting the record is not. The test I apply: does the user have information the system lacks? If yes, reject and ask. If the system can derive the value with certainty, derive it.
The bypass pattern (a custom permission checked in every rule formula) is worth adding early if you run migrations. Retrofitting it means editing every rule in the org. Adding AND NOT($Permission.Bypass_Validation) while you write them costs nothing.
Frequently asked
Why does the error name a field I did not touch?
The bracketed field is the rule’s errorDisplayField, where the author chose to display the message. It has nothing to do with which field made the formula evaluate true.
Do validation rules run on Data Loader and Bulk API operations?
Yes. They fire on API inserts and updates the same as UI saves. There is no API-only exemption. Only an explicit bypass built into the formula changes that.
Can I catch this exception in Apex?
With a DML statement you catch DmlException and read getDmlMessage(0) and getDmlFields(0). With a Database method and allOrNone false you get no exception at all. Inspect the SaveResult array. The second approach is better for bulk work.
The rule is inactive but the error still appears.
Then it is not that rule. Check for a second rule with similar wording, a flow custom error, or an addError(). Duplicated message text across a rule and a trigger is common in orgs that moved logic from one to the other and left the original active.
Does deactivating a validation rule affect existing records?
No. Rules evaluate only at save time, so records already stored in violation stay stored. Activating a rule does not retroactively flag anything either. Find those with a report or a SOQL query.
Reference: Core Data Types Used in API Calls (SOAP API Developer Guide), ValidationRule (Tooling API), ValidationRule (Metadata API Developer Guide), plus DML Statements vs. Database Class Methods, SaveResult Class, SObject Class, Triggers and Order of Execution, and Debug Log. Also “Define Validation Rules” in Salesforce Help.
