ENTITY_FAILED_IFLASTMODIFIED_ON_UPDATE_CHECK: record has been modified since you retrieved it
You sent an update with an `If-Unmodified-Since` header (or the API equivalent), and the record was modified by someone else after your read. The platform protected against the lost-update — refreshing your read and reapplying the change is the right behaviour.
Also seen asENTITY_FAILED_IFLASTMODIFIED_ON_UPDATE_CHECK·record has been modified since you retrieved it·If-Unmodified-Since·optimistic locking salesforce
This is Salesforce's optimistic concurrency control. When you GET a record and pass the LastModifiedDate back on the next PATCH/PUT (via the If-Unmodified-Since header in REST or the equivalent in SOAP), the platform checks: has someone else modified the record since you read it? If yes, your update is rejected to prevent overwriting their changes.
What's happening
T0: User A reads Account 001x — LastModifiedDate = 2026-04-30T10:00
T1: User B reads Account 001x — same LastModifiedDate
T2: User A updates Description = "from A" — LastModifiedDate = 2026-04-30T10:01
T3: User B updates Description = "from B" with If-Unmodified-Since: 2026-04-30T10:00
→ ENTITY_FAILED_IFLASTMODIFIED_ON_UPDATE_CHECK
User B's read was stale. The platform refuses the write to avoid silently losing User A's changes.
The right behaviour
When you see this error, the canonical retry pattern is:
- Re-fetch the record (get the new
LastModifiedDate) - Re-apply your intended change
- Send the update with the new If-Unmodified-Since
- If it fails again, surface a conflict to the user
async function patchRecord(id, fields, retries = 1) {
const current = await fetch(`/services/data/v60.0/sobjects/Account/${id}`);
const lastMod = current.headers.get('Last-Modified');
const res = await fetch(`/services/data/v60.0/sobjects/Account/${id}`, {
method: 'PATCH',
headers: { 'If-Unmodified-Since': lastMod, 'Content-Type': 'application/json' },
body: JSON.stringify(fields)
});
if (res.status === 412 && retries > 0) {
// 412 Precondition Failed — record changed; retry
return patchRecord(id, fields, retries - 1);
}
return res;
}
One retry is usually enough. If it fails twice, two writers are racing in earnest — the right answer is to show the user "this changed under you" and let them decide.
Don't drop the header
You can avoid this error entirely by not sending If-Unmodified-Since. Then concurrent writes silently overwrite each other. That's almost never what you want, but the platform doesn't force the header — it's opt-in.
The reverse: code that forgets to send the header has a different bug — the lost-update problem. You see "the user's changes disappeared" reports without any error fired.
In Apex
Apex doesn't expose If-Unmodified-Since directly. Equivalent patterns:
Account fresh = [SELECT Id, LastModifiedDate FROM Account WHERE Id = :recordId];
if (fresh.LastModifiedDate != originalReadModifiedDate) {
throw new ConflictException('Account was modified since you opened the form.');
}
fresh.Name = newName;
update fresh;
The check happens in your code, not in the database. So between the SELECT and the UPDATE there's still a race window — but a much smaller one than between the user's GET and your update.
When it fires unexpectedly
If your code sends If-Unmodified-Since: <a date> and the record's actual LastModifiedDate is earlier than that date, you still get the error. The platform compares "system says record was modified at T1" vs "your header says T0; reject if T1 > T0." The header must be exactly the LastModifiedDate from the previous GET, not "now."
