System.DmlException: Insert failed. First exception on row 0; first error: FIELD_CUSTOM_VALIDATION_EXCEPTION, Revenue must be greater than zero: [AnnualRevenue]
What does this error mean?
Salesforce validation rules evaluate field values on a record whenever a DML operation (insert, update, upsert) is attempted. If the rule's error condition formula evaluates to true, Salesforce blocks the DML and throws this exception. The error message and field name in brackets both come directly from the validation rule configuration.
This error appears in both the UI (as a page-level message) and in Apex (as a System.DmlException with status code FIELD_CUSTOM_VALIDATION_EXCEPTION). In Apex, the getFields() method on the error object returns the API name of the field the rule is attached to.
Anatomy of the exception message
FIELD_CUSTOM_VALIDATION_EXCEPTION
[AnnualRevenue] — API name of field the rule targets
Not always a bug: This error is often correct behaviour — a validation rule is doing exactly what it was designed to do. The fix may be updating your Apex code to set the right field values, not removing the validation rule.
Common Causes
1. Apex Code Setting Incorrect Field Values
Automations, triggers, or integrations that create or update records programmatically often skip populating required fields that a validation rule checks. A rule that says "Phone is required when Status is Active" will block any Apex that sets Status to Active without also setting Phone.
2. Validation Rule Added After Existing Automation
A new validation rule is deployed to an org where existing Apex automations were never updated to account for it. The automation had previously set a field value that now violates the new rule.
3. Integration Payloads Missing Required Fields
An external system pushing records via the REST or SOAP API does not include all fields that validation rules require. The partial payload passes schema validation but fails business logic validation.
4. Test Data Not Matching Validation Criteria
Test classes that create minimal records (just enough to satisfy required fields) fail because a validation rule's criteria require additional fields to be populated with specific values.
5. allOrNone = true Rolling Back Valid Records
With the default allOrNone = true DML behaviour, a single record in a list failing validation causes the entire operation to roll back — including records that would have passed. This surfaces the error even when most records are perfectly valid.
How to Debug It
Before writing any fix, you need to identify exactly which validation rule is firing and which field value is the problem. Follow this sequence:
Read the full error message
The text in the exception is your validation rule's exact error message. Search for it in Setup → Validation Rules to find the culprit rule instantly.
Check the field name in brackets
The API field name inside [] tells you exactly which field the rule evaluates. That's the field your code likely has a wrong value on.
Log the failing record's field values
Use JSON.serializePretty(record) in a debug log to inspect every field value on the record right before the DML call.
Read the validation rule formula
Go to Setup → Object Manager → [Object] → Validation Rules → [Rule Name]. Read the Error Condition Formula to understand exactly what values will trigger it.
Compare record values against the formula
Manually evaluate the formula with your record's field values. If it evaluates to true, the rule fires. Adjust your Apex to produce values that make the formula false.
How to Fix It
Solution 1: Inspect and Fix the Record's Field Values
The most direct fix — make sure your Apex sets the field values the validation rule requires. Log the record before DML to see exactly what you're sending.
Account acc = new Account(
Name = 'Acme Corp',
Status__c = 'Active'
// ❌ Missing Phone — validation rule requires it when Active
);
// Log before DML to see exactly what you're inserting
System.debug(JSON.serializePretty(acc));
// ✅ Fix: populate the field the rule requires
acc.Phone = '415-555-0100';
insert acc;
Solution 2: Handle Validation Errors Gracefully with Partial DML
Use Database.insert(allOrNone:false) to process valid records and collect failures separately, so one bad record doesn't block the whole batch. Log or surface validation errors clearly for each failing record.
List<Database.SaveResult> results =
Database.insert(accountList, false); // allOrNone = false
List<String> errors = new List<String>();
for (Integer i = 0; i < results.size(); i++) {
if (!results[i].isSuccess()) {
for (Database.Error err : results[i].getErrors()) {
// Detect validation rule failures specifically
if (err.getStatusCode() ==
StatusCode.FIELD_CUSTOM_VALIDATION_EXCEPTION) {
errors.add(
'Row ' + i + ': Validation failed — '
+ err.getMessage()
+ ' Field(s): ' + err.getFields()
);
}
}
}
}
if (!errors.isEmpty()) {
System.debug(LoggingLevel.WARN,
'Validation errors:\n' + String.join(errors, '\n'));
}
Solution 3: Bypass Validation in Test Setup Using @TestSetup
If validation rules are blocking your test's setup data, create records that satisfy the rule's conditions — don't disable the rule. Your tests should mirror real-world data scenarios.
@isTest
private class AccountServiceTest {
@TestSetup
static void makeData() {
// ✅ Include all fields validation rules require
Account acc = new Account(
Name = 'Test Corp',
Phone = '415-555-0100', // Required by VR when Active
Status__c = 'Active',
AnnualRevenue = 100000 // Must be > 0 per another VR
);
insert acc;
}
@isTest
static void testUpdateAccount() {
Account acc = [SELECT Id, Name FROM Account LIMIT 1];
Test.startTest();
acc.Name = 'Updated Corp';
update acc;
Test.stopTest();
System.assertEquals('Updated Corp',
[SELECT Name FROM Account WHERE Id = :acc.Id].Name);
}
}
Solution 4: Use a Bypass Flag for System Automation
When a legitimate system automation (like a data migration or integration) must bypass a validation rule, the cleanest approach is to add a bypass checkbox field to the object and update the rule's formula to skip when the flag is set.
/* Original rule: fires when AnnualRevenue is blank or zero */
AND(
ISBLANK(AnnualRevenue),
NOT(Bypass_Validation__c) /* Skip rule when bypass flag is true */
)
for (Account acc : accountsToMigrate) {
acc.Bypass_Validation__c = true; // Signals rule to skip
}
update accountsToMigrate;
// Re-enable after migration
for (Account acc : accountsToMigrate) {
acc.Bypass_Validation__c = false;
}
update accountsToMigrate;
Pro Tip: In Setup, go to Object Manager → [Object] → Validation Rules and use the Active checkbox filter to see all active rules at a glance. You can also temporarily deactivate a specific rule in a sandbox to confirm it's the culprit before writing any fix code — much faster than log hunting.