System.DmlException: Insert failed (or Update / Upsert / Delete failed)
A DML statement (insert/update/upsert/delete) failed. The exception message contains a "first exception on row N" line that tells you which record blew up and why — that's where you actually look.
Also seen asDmlException·Insert failed·Update failed·Upsert failed
DmlException is the umbrella exception for any failure during insert, update, upsert, delete, merge, or their Database.* equivalents. Useful diagnostic info is buried inside its message, not the type name.
Read the right part of the message
System.DmlException: Insert failed. First exception on row 2;
first error: REQUIRED_FIELD_MISSING, Required fields are missing: [Email]: [Email]
There are three pieces:
| Part | Meaning |
|---|---|
Insert failed | Which DML operation |
row 2 | The 0-indexed position of the failing record in the list you passed |
REQUIRED_FIELD_MISSING ... | The actual reason — almost always a known Salesforce error code |
Look up the inner code (REQUIRED_FIELD_MISSING, FIELD_CUSTOM_VALIDATION_EXCEPTION, INSUFFICIENT_ACCESS_OR_READONLY, etc.) — that's the page that explains your specific failure.
The hidden problem: all-or-nothing
By default, insert myList is all-or-nothing: if record 2 fails, records 0 and 1 also roll back. In a 200-record bulk operation, one bad row can trash 199 good ones.
If you want partial success (commit the good rows, collect errors for the bad ones), use the Database.* overloads with allOrNone = false:
Database.SaveResult[] results = Database.insert(myList, false);
for (Integer i = 0; i < results.size(); i++) {
Database.SaveResult sr = results[i];
if (!sr.isSuccess()) {
for (Database.Error err : sr.getErrors()) {
System.debug(
'Row ' + i + ' (' + myList[i].Id + '): ' +
err.getStatusCode() + ' — ' + err.getMessage()
);
}
}
}
Now you get a per-row report instead of one fatal exception.
Trapping the exception cleanly
If you do want to halt on any failure, at least log richly:
try {
insert myList;
} catch (DmlException e) {
for (Integer i = 0; i < e.getNumDml(); i++) {
System.debug(
'Row ' + e.getDmlIndex(i) +
' status: ' + e.getDmlStatusCode(i) +
' message: ' + e.getDmlMessage(i) +
' fields: ' + e.getDmlFieldNames(i)
);
}
throw e;
}
The getDml* family is the structured version of the human-readable message. Use it when you're surfacing errors to a Lightning component or logging to a custom error table.
A common cause that isn't a validation rule
If the inner code is INSUFFICIENT_ACCESS_OR_READONLY and you're sure the user has CRUD on the object, check:
- Field-Level Security on each updated field
- A formula field accidentally listed in the SELECT/UPDATE
- A record-level sharing rule the user doesn't satisfy
- An
AuraEnabledmethod that didn't declarewith sharingand is now running with elevated permissions you didn't intend
