Salesforce Common Errors: Causes and Fixes
How to read a Salesforce error
Every Salesforce error carries a status code, a message, and a location, and those three parts usually point to the fix. This guide covers the errors admins and developers meet most often, grouped by where they appear. Each entry explains what the error means, why it happens, and how to resolve it.
Limit figures are taken from the Salesforce Execution Governors and Limits page for Winter ’27 (API version 68.0). Limits change between releases, so check that page if your org runs a different version.
Anatomy of an error message
A typical save error looks like this:
Update failed. First exception on row 0 with id 006XXXXXXXXXXXX; first error:
FIELD_CUSTOM_VALIDATION_EXCEPTION, Close Date cannot be in the past: [CloseDate]
| Part | Example | What it tells you |
|---|---|---|
| Operation | Update failed | Which action failed: insert, update, upsert or delete |
| Row and ID | row 0 with id 006... | Which record in the batch failed |
| Status code | FIELD_CUSTOM_VALIDATION_EXCEPTION | The category of failure, which is what this guide is organised by |
| Message | Close Date cannot be in the past | The specific reason, often written by your own admin |
| Field | [CloseDate] | The field involved, when there is one |
When an error mentions a trigger or flow, read to the end of the message. The first line names the automation that failed. The last line names the underlying cause.
Where to look for more detail
| Tool | Where to find it | Use it for |
|---|---|---|
| Debug logs | Setup → Debug Logs, add a trace flag for the affected user | Every query, DML statement, automation and limit used in a transaction |
| Developer Console | Gear menu → Developer Console | Running queries, reading logs, running tests |
| Failed flow interviews | Setup → Paused and Failed Flow Interviews | The element and record a flow failed on |
| Flow error emails | Sent to the flow’s last modifier or the Apex exception email recipients | A step-by-step trace of the failed flow |
| Apex Jobs | Setup → Apex Jobs | Failures in batch, queueable, future and scheduled Apex |
| Deployment Status | Setup → Deployment Status | Component and test failures in a deployment |
| Login History | Setup → Login History | Why a user or integration could not log in |
| Setup Audit Trail | Setup → View Setup Audit Trail | Recent configuration changes that may have introduced the error |
| System Overview | Setup → System Overview | API usage and storage against org limits |
The fastest diagnostic question is usually “what changed?” If an error is new, check the Setup Audit Trail and recent deployments before reading code.
Record save (DML) errors
These errors appear when a record is created, updated or deleted, whether through the UI, a data load, a flow, Apex or an API. The status code is the same in every channel.
FIELD_CUSTOM_VALIDATION_EXCEPTION
A validation rule, a flow custom error, or an addError() call in a trigger blocked the save. The message text is whatever the rule’s author wrote.
Common causes
- The record does not meet a validation rule’s criteria.
- An integration or data load skips fields that users fill in through the UI.
- A rule was added after old records were created, so any edit to an old record now fails.
- Automation updates an unrelated field and trips a rule the designer did not consider.
- Apex test classes create data that no longer satisfies a new rule.
How to fix
- Search for the message text in Object Manager → Validation Rules to find the rule.
- Correct the data so it satisfies the rule.
- For integrations, add a bypass using a custom permission, for example
NOT($Permission.Bypass_Validation). - For legacy records, limit the rule with
ISNEW()orISCHANGED(Field__c)so it only fires on relevant edits. - Update test data factories so tests create valid records.
REQUIRED_FIELD_MISSING
A field that is required at the database level was left blank. The message lists the fields, for example Required fields are missing: [LastName].
Common causes
- A standard required field is missing, such as Lead
LastNameandCompany, or OpportunityName,StageNameandCloseDate. - A custom field is marked Required in its field definition.
- A CSV column is unmapped or blank for some rows.
- A flow’s Create Records element or Apex code does not set the field.
How to fix
- Populate the field named in the brackets.
- Check the column mapping in Data Loader or Data Import Wizard.
- Add a default value to the field if a sensible default exists.
- If the field should only be required for users, remove the field-level requirement and make it required on the page layout instead. Layout requirements do not apply to API or Apex.
DUPLICATE_VALUE
A field marked Unique already holds the value on another record. The message includes the ID of the existing record.
Common causes
- An insert was used where an update or upsert was intended.
- The same value appears twice in one load file.
- A trigger or flow copies a value into a unique field.
- A job was re-run after a partial failure and tried to create the same records again.
How to fix
- Open the record ID from the message and decide whether to update it or merge.
- Use upsert with an external ID field so repeated loads update rather than insert.
- Remove duplicates from the source file before loading.
- If the field does not need to be unique, clear the Unique setting.
DUPLICATES_DETECTED
A duplicate rule with the Block action stopped the save. This is different from DUPLICATE_VALUE, which is a field-level constraint.
Common causes
- A matching rule found a similar Lead, Contact or Account, often by fuzzy name and email matching.
- An integration user is subject to a rule designed for manual data entry.
- The matching rule is too broad and flags records that are not true duplicates.
How to fix
- Review the rule in Setup → Duplicate Rules and the matching rule it uses.
- Change the action from Block to Allow with an alert if a warning is enough.
- Add a rule condition to exclude the integration user’s profile.
- In Apex, allow the save explicitly:
Database.DMLOptions opts = new Database.DMLOptions();
opts.DuplicateRuleHeader.allowSave = true;
Database.insert(newContact, opts);
STRING_TOO_LONG
A text value is longer than the field allows. The message gives the field and its maximum, for example data value too large (max length=80).
Common causes
- Source data from another system uses longer fields.
- Automation concatenates several values into one field.
- Record
Namefields are limited to 80 characters on most objects.
How to fix
- Shorten the value in the source, or truncate it in a formula with
LEFT()or in Apex withabbreviate()orleft(). - Increase the field length, or convert it to a Long Text Area.
- For loads where truncation is acceptable, set
allowFieldTruncationinDatabase.DMLOptionsor theAllowFieldTruncationHeaderin the API.
INVALID_CROSS_REFERENCE_KEY
The record references an ID that does not exist or is not valid for that field.
Common causes
- An ID was hardcoded from another org. Record IDs differ between sandbox and production.
- The record type ID does not belong to the object, or is not assigned to the user’s profile.
- The owner is a queue that does not support the object.
- The referenced record was deleted.
- An opportunity product references a price book entry from a different price book.
How to fix
- Query the ID in the target org to confirm it exists and is the right object type.
- Replace hardcoded IDs with lookups by name:
Id rtId = Schema.SObjectType.Account
.getRecordTypeInfosByDeveloperName()
.get('Business_Account')
.getRecordTypeId();
- Assign the record type to the user’s profile or permission set.
- Add the object to the queue’s supported objects.
INSUFFICIENT_ACCESS_ON_CROSS_REFERENCE_ENTITY
The user can edit the record being saved but lacks access to a related record the save touches.
Common causes
- The user has no access to the parent in a lookup or master-detail relationship.
- The new owner lacks Read permission on the object.
- A trigger or flow running as the user updates another record the user cannot edit.
- The record type being assigned is not available to the user.
How to fix
- Use the ID in the message to identify the related record.
- Check that record’s sharing with the Sharing button, then grant access through a role, sharing rule or permission set.
- Confirm the new owner’s profile has access to the object.
- If the automation should work regardless of the user, run it in system context. For flows, set How to Run the Flow to System Context Without Sharing. For Apex, use a
without sharingclass.
INSUFFICIENT_ACCESS_OR_READONLY
The user has no right to perform this operation on the record itself.
Common causes
- The profile lacks Create, Edit or Delete on the object.
- Org-wide defaults are Private or Read Only and nothing shares the record with edit access.
- The user is changing ownership without the Transfer Record permission.
- An Experience Cloud or guest user is restricted by external sharing settings.
How to fix
- Check object permissions on the user’s profile and permission sets.
- Check record access: open the record, use Sharing, and review why the user does or does not have access.
- Grant the missing permission through a permission set rather than editing the profile.
- For automation, consider running in system context.
FIELD_INTEGRITY_EXCEPTION
A field value is invalid in context, even though its format is correct.
Common causes
- A lookup field holds the ID of the wrong object type.
- State and Country/Territory Picklists are enabled and the value does not match a configured state or country.
- An opportunity product’s price book entry does not match the opportunity’s price book.
- A related record is inactive, such as an inactive price book or product.
How to fix
- Read the field name in the message and check the value against what that field accepts.
- For addresses, use the exact state and country names or codes from Setup → State and Country/Territory Picklists.
- Make sure the opportunity’s price book is set before adding products, and that entries come from that price book.
FIELD_FILTER_VALIDATION_EXCEPTION
The selected lookup value does not satisfy the field’s lookup filter. Users see “Value does not exist or does not match filter criteria.”
Common causes
- A required lookup filter excludes the chosen record.
- The related record changed after it was selected and no longer meets the filter.
- An integration sets a lookup without knowing about the filter.
How to fix
- Open the lookup field definition and read the filter criteria.
- Choose a record that matches, or correct the related record so it matches.
- Change the filter from Required to Optional if a warning is enough.
- Exempt specific profiles by adding a
$Profilecondition to the filter.
INVALID_OR_NULL_FOR_RESTRICTED_PICKLIST
The value is not allowed in a restricted picklist. The message reads bad value for restricted picklist field: <value>.
Common causes
- The value is not in the picklist’s value set.
- The value exists but is not assigned to the record’s record type.
- The load file uses the label where the API name is expected.
- The value has been deactivated.
- Extra spaces or spelling differences in the source data.
How to fix
- Add the value to the picklist and to the relevant record types.
- Use API names in data loads and integrations.
- Clean the source data so values match exactly.
- If free-form values are acceptable, clear “Restrict picklist to the values defined in the value set.”
INVALID_FIELD_FOR_INSERT_UPDATE
The request tries to write to a field that cannot be written. The message reads Unable to create/update fields: <field>.
Common causes
- The field is a formula, roll-up summary, auto-number or system field.
- Field-level security makes the field read-only for the user.
- A master-detail field is being changed and reparenting is not allowed.
- Audit fields such as
CreatedDateare included without the right permission.
How to fix
- Remove the field from the mapping or payload.
- Grant edit access to the field through a permission set.
- Enable “Allow reparenting” on the master-detail field if children should move between parents.
- To load audit fields, enable “Set Audit Fields upon Record Creation” and assign that permission to the loading user.
UNABLE_TO_LOCK_ROW
Salesforce could not get an exclusive lock on a record within about 10 seconds. The message reads unable to obtain exclusive access to this record.
Common causes
- Two transactions update the same record, or children of the same parent, at the same time.
- Bulk API parallel mode splits children of one parent across batches.
- Data skew: one account with more than 10,000 children, or one user owning a very large number of records.
- Roll-up summary fields and master-detail relationships lock the parent on every child save.
- Slow triggers or flows hold locks for a long time.
- Scheduled jobs overlap with integrations or each other.
How to fix
- Sort load files by parent ID so each parent’s children land in the same batch.
- Use serial mode in Bulk API, or reduce the batch size.
- Reschedule jobs so they do not run at the same time.
- Add retry logic to integrations. A retry a few seconds later usually succeeds.
- Reduce skew by spreading children across more parents or owners.
- Shorten transactions by removing unnecessary automation from the object.
CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY
A trigger or flow threw an unhandled exception during the save. This code is a wrapper. The real cause follows in the message.
CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY, AccountTrigger: execution of AfterInsert
caused by: System.NullPointerException: Attempt to de-reference a null object
Class.AccountHandler.setRegion: line 42, column 1
Common causes
- An Apex exception inside a trigger or its handler class.
- A managed package trigger failing on your data.
- A record-triggered flow failing on an element.
How to fix
- Read the text after
caused byand look up that exception in this guide. - Go to the class and line number named in the message.
- Reproduce the save with a debug log running to see the values involved.
- For managed package triggers, check the package configuration and contact the vendor.
MIXED_DML_OPERATION
Setup objects and regular objects were modified in the same transaction. Setup objects include User, UserRole, Group, GroupMember, PermissionSet, PermissionSetAssignment and QueueSObject.
Common causes
- Creating a user and a contact or account in one transaction.
- A record trigger that assigns permission sets or adds group members.
- A test method that inserts users and then test data.
How to fix
- Move the setup object DML into a
@futuremethod or Queueable job:
@future
public static void assignPermissionSet(Id userId, Id permSetId) {
insert new PermissionSetAssignment(
AssigneeId = userId, PermissionSetId = permSetId);
}
- In tests, wrap one of the two operations in
System.runAs(). - In flows, move the setup object change onto an asynchronous path.
ENTITY_IS_DELETED
The operation targets a record that has been deleted.
Common causes
- The record was deleted between being queried and being updated.
- An integration holds a stale ID.
- The record’s parent was deleted, which removed the child through cascade delete.
- A merge removed the losing record.
How to fix
- Check the Recycle Bin and restore the record if it was deleted by mistake.
- In Apex, confirm deletion with a query using
ALL ROWSandIsDeleted = true. - Refresh the integration’s stored IDs. After a merge, use the surviving record’s ID.
MALFORMED_ID
A value supplied as an ID is not a valid Salesforce ID for that field.
Common causes
- The value is not 15 or 18 characters, often because of trailing spaces or truncation.
- A name was supplied where an ID is required.
- The ID belongs to a different object type than the lookup expects.
- A spreadsheet altered the IDs. Excel treats 15-character IDs as case-insensitive in lookups.
How to fix
- Use 18-character IDs in all files and integrations.
CASESAFEID(Id)converts a 15-character ID in a formula. - Check the three-character prefix:
001Account,003Contact,006Opportunity,00QLead,005User,500Case. - Trim whitespace from ID columns before loading.
SELF_REFERENCE_FROM_TRIGGER
A trigger tried to update or delete a record that is already being processed by a trigger in the same transaction.
Common causes
- An after trigger runs DML on the same records it is processing.
- A trigger on object A updates object B, whose trigger updates A again.
- A before delete trigger tries to update the record being deleted.
How to fix
- To change fields on the triggering record, set them in a before trigger with no DML:
trigger AccountTrigger on Account (before update) {
for (Account a : Trigger.new) {
a.Description = 'Updated'; // no update statement needed
}
}
- Break circular updates with a static flag that prevents the second pass.
- Review flows and triggers on both objects together to see the full chain.
STORAGE_LIMIT_EXCEEDED
The org has run out of data or file storage, so new records cannot be created.
Common causes
- Accumulated tasks, email messages, logs or history records.
- A large data load.
- Small sandboxes filling up quickly.
- Many file versions stored as ContentVersion records.
How to fix
- Open Setup → Storage Usage to see which objects use the most space.
- Delete or archive old records, starting with the largest objects.
- Remove old file versions and unused attachments.
- Move historical data to Big Objects or an external store.
- Purchase additional storage if the growth is legitimate.
ENTITY_IS_LOCKED
The record is locked, normally because it is pending in an approval process.
Common causes
- A user tries to edit a record awaiting approval.
- Automation running as that user tries to update the locked record.
- Apex locked the record with
Approval.lock().
How to fix
- Approve, reject or recall the approval request to release the lock.
- Ask an administrator or the assigned approver to make the edit, if the approval process allows approvers to edit.
- Run the automation in system context so it can update locked records.
- Use
Approval.unlock()in Apex. This requires “Enable record locking and unlocking in Apex” in Process Automation Settings.
Governor limit errors
Governor limits cap what one transaction can consume, and exceeding one throws a System.LimitException that cannot be caught with try/catch. The whole transaction rolls back. The fix is always to use less of the resource, not to handle the error.
A transaction includes everything that runs from one save: triggers, flows, validation, and any automation they set off. Limits are shared across all of it.
| Limit | Synchronous | Asynchronous | Error message |
|---|---|---|---|
| SOQL queries | 100 | 200 | Too many SOQL queries: 101 |
| Records retrieved by SOQL | 50,000 | 50,000 | Too many query rows: 50001 |
| DML statements | 150 | 150 | Too many DML statements: 151 |
| Records processed by DML | 10,000 | 10,000 | Too many DML rows: 10001 |
| CPU time | 10,000 ms | 60,000 ms | Apex CPU time limit exceeded |
| Heap size | 10 MB | 25 MB | Apex heap size too large |
| Callouts | 100 | 100 | Too many callouts: 101 |
| Future calls | 50 | 0 in batch and future, 50 in queueable | Too many future calls: 51 |
| Trigger recursion depth | 16 | 16 | Maximum trigger depth exceeded |
Source: Execution Governors and Limits, Winter ’27. Certified managed packages get their own allowance for most of these limits. CPU time and heap size are shared across the whole transaction.
Too many SOQL queries: 101
The transaction ran more than 100 SOQL queries. In asynchronous Apex the limit is 200 and the message shows 201.
Common causes
- A query inside a
forloop, which runs once per record. - A helper method that queries, called once per record from a trigger.
- Several triggers, flows and processes on one object, each running its own queries.
- A trigger that re-fires itself, repeating its queries on each pass.
- A flow with a Get Records element inside a loop.
How to fix
- Move queries outside loops. Collect IDs first, query once, and read results from a map:
// Before: one query per record
for (Contact c : Trigger.new) {
Account a = [SELECT Industry FROM Account WHERE Id = :c.AccountId];
}
// After: one query for all records
Set<Id> accountIds = new Set<Id>();
for (Contact c : Trigger.new) {
accountIds.add(c.AccountId);
}
Map<Id, Account> accounts = new Map<Id, Account>(
[SELECT Industry FROM Account WHERE Id IN :accountIds]);
- Use relationship queries to fetch parent and child data in one statement.
- Keep one trigger per object and route logic through a handler class.
- Cache query results in a static variable when several methods need the same data.
- In a debug log, count the
SOQL_EXECUTE_BEGINlines to see which query repeats. - In flows, place Get Records before the loop and filter the collection inside it.
Too many DML statements: 151
The transaction issued more than 150 DML statements. Each insert, update, delete or upsert call counts once, whatever the number of records.
Common causes
- A DML statement inside a loop.
- A flow with Create, Update or Delete Records inside a loop.
- Several pieces of automation each updating the same object separately.
- Heavy use of
Database.setSavepoint()andDatabase.rollback(), which also count.
How to fix
- Add records to a list inside the loop and run one DML statement after it:
List<Task> tasks = new List<Task>();
for (Opportunity o : Trigger.new) {
tasks.add(new Task(WhatId = o.Id, Subject = 'Follow up'));
}
insert tasks;
- When several methods update the same records, collect changes in a
Map<Id, SObject>and update once. - In flows, use Assignment to add records to a collection, then one Update Records element after the loop.
Too many query rows: 50001
Queries in the transaction returned more than 50,000 records in total. Rows from subqueries count toward the total.
Common causes
- A query with no
WHEREclause or a very broad one. - Many moderate queries that add up across the transaction.
- Parent-child subqueries that pull large numbers of child rows.
How to fix
- Add filters and a
LIMITso the query returns only what the logic needs. - Use aggregate queries such as
COUNT()orSUM()instead of retrieving rows to count or total them. - For genuine large-volume work, use Batch Apex. A
Database.QueryLocatorcan return up to 50 million records, processed in chunks.
Too many DML rows: 10001
The transaction tried to insert, update or delete more than 10,000 records in total.
Common causes
- A trigger that updates every child of a parent with a very large number of children.
- A one-off script run in Anonymous Apex against a large data set.
- Cascading automation where each update sets off further updates.
How to fix
- Move the work to Batch Apex, which gives each chunk its own limits.
- Chain Queueable jobs to process the records in smaller sets.
- Narrow the scope so only records that need to change are updated.
Apex CPU time limit exceeded
The transaction used more than 10 seconds of server CPU time, or 60 seconds in asynchronous Apex. CPU time covers Apex, flows and package code. Time spent in the database and waiting on callouts is not counted.
Common causes
- Nested loops over large collections.
- Too much automation on one object: several triggers, flows and legacy processes firing together.
- Recursion, where automation re-fires several times for one save.
- Heavy string handling or JSON serialisation on large data.
- Data loads at 200 records per batch through heavy automation.
- Managed package code, which shares the same CPU limit.
How to fix
- Replace nested loops with map lookups:
// Before: loop inside a loop
for (Contact c : contacts) {
for (Account a : accounts) {
if (c.AccountId == a.Id) { /* ... */ }
}
}
// After: one pass with a map
Map<Id, Account> accountMap = new Map<Id, Account>(accounts);
for (Contact c : contacts) {
Account a = accountMap.get(c.AccountId);
}
- Exit early when the fields the logic depends on have not changed.
- Consolidate automation. Use before-save flows for same-record field updates, since they are much cheaper than after-save updates.
- Move non-urgent work to Queueable or Batch Apex, where the limit is 60 seconds.
- For data loads, lower the batch size so each transaction does less work.
- Use the debug log’s timeline or
Limits.getCpuTime()to find the slow section.
Apex heap size too large
The transaction held more than 10 MB of data in memory, or 25 MB in asynchronous Apex.
Common causes
- Querying many records with many fields into one list.
- Loading files, Blobs or base64 strings into variables.
- Building or parsing very large JSON or XML strings.
- Static collections that keep growing through the transaction.
- Large callout responses. Request and response bodies count toward heap.
How to fix
- Use a SOQL
forloop, which processes records in chunks of 200 rather than holding them all:
for (List<Account> chunk : [SELECT Id, Name FROM Account]) {
// work on 200 records at a time
}
- Select only the fields the code uses.
- Clear collections and set large variables to
nullonce they are no longer needed. - Mark Visualforce controller variables
transientwhen they do not need to persist. - Move the work to asynchronous Apex for the higher limit.
- Avoid processing large files in Apex. Hand them to an external service where possible.
Too many callouts: 101
The transaction made more than 100 HTTP or web service callouts. There is also a 120-second cap on total callout time per transaction.
Common causes
- One callout per record inside a loop.
- A batch job with a large scope size making a callout for each record.
How to fix
- Send records in bulk if the external API accepts lists.
- Spread the work across Batch Apex with
Database.AllowsCalloutsand a small scope size. - Chain Queueable jobs, each handling a set of callouts.
Too many future calls: 51
The transaction invoked more than 50 @future methods.
Common causes
- A
@futuremethod called once per record in a trigger loop. - Several triggers each calling their own future methods in one transaction.
How to fix
- Pass a set of IDs to a single future call instead of one call per record:
@future
public static void syncAccounts(Set<Id> accountIds) {
// process all accounts in one call
}
- Use a Queueable job, which accepts complex types and can be chained.
Maximum trigger depth exceeded
Triggers fired recursively more than 16 levels deep.
Common causes
- A trigger updates records of its own object, which fires the trigger again.
- A trigger on object A updates object B, whose trigger updates A.
- A flow and a trigger that each update the record the other reacts to.
How to fix
- Track processed records in a static set and skip them on later passes:
public class TriggerGuard {
public static Set<Id> processed = new Set<Id>();
}
// In the handler
for (Account a : Trigger.new) {
if (TriggerGuard.processed.contains(a.Id)) continue;
TriggerGuard.processed.add(a.Id);
// logic
}
- Compare
Trigger.oldMapwithTrigger.newand act only when a relevant field changed. - Set fields in before triggers, which need no DML and so do not re-fire the trigger.
Apex runtime exceptions
These are unhandled exceptions in custom code, and unlike limit errors they can be caught and prevented with defensive coding. The stack trace names the class and line, which is the place to start.
NullPointerException: Attempt to de-reference a null object
The code called a method or read a property on a variable that holds null.
Common causes
- A list, set or map was declared but never initialised.
Map.get()returnednullbecause the key was not in the map.- A string method such as
trim()ortoLowerCase()was called on a blank field. Trigger.oldMapwas used in an insert context, where it isnull.- A custom setting or custom metadata record that the code expects does not exist in this org.
How to fix
- Go to the line number in the stack trace and identify which variable is null.
- Initialise collections when declaring them:
List<Account> accts = new List<Account>(); - Use the safe navigation operator, which returns
nullinstead of throwing:
String city = accountMap.get(c.AccountId)?.BillingCity;
- Use
String.isBlank()before calling string methods on field values. - Check
map.containsKey()before using the result ofget(). - Create the missing custom setting or metadata records in the target org.
QueryException: List has no rows for assignment to SObject
A query assigned to a single record variable returned nothing.
Common causes
- No record matches the filter.
- The record exists but the running user cannot see it, in a
with sharingclass. - A test method has not created the data. Tests cannot see org data by default.
- The record was deleted, or the ID came from another org.
How to fix
- Query into a list and check whether it is empty:
List<Account> accts = [SELECT Id FROM Account WHERE Name = :name LIMIT 1];
Account a = accts.isEmpty() ? null : accts[0];
- Create the required records in the test’s setup method.
- Check the user’s access to the record if it exists but is not returned.
QueryException: List has more than 1 row for assignment to SObject
A query assigned to a single record variable returned several records.
Common causes
- The filter is not unique, for example a query by name where duplicates exist.
- Duplicate records were created after the code was written.
How to fix
- Add
ORDER BYandLIMIT 1if any one match will do. - Query into a list and handle multiple results deliberately.
- Filter on a unique field such as an ID or external ID.
- Clean up the duplicate records.
QueryException: Non-selective query against large object type
A query inside a trigger ran against an object with more than 200,000 records and its filter could not use an index efficiently.
Common causes
- The
WHEREclause filters on a field that is not indexed. - The filter compares against
null, often because a bind variable or ID set contains a null value. - The filter uses a leading wildcard such as
LIKE '%text'. - The filter uses negative operators such as
!=orNOT IN. - The indexed filter still matches too large a share of the records.
How to fix
- Filter on indexed fields:
Id,Name,OwnerId,CreatedDate,RecordTypeId, lookup fields, and fields marked External ID or Unique. - Remove nulls from ID sets before using them in a query.
- Mark a frequently filtered custom field as External ID, or ask Salesforce Support for a custom index.
- Use the Query Plan tool in Developer Console to check whether a query is selective.
- Archive old records to reduce the object’s size.
SObjectException: SObject row was retrieved via SOQL without querying the requested field
The code read a field that was not in the query’s SELECT list. The message names the field.
Common causes
- The field was left out of the query.
- A record queried in one method is passed to another that needs more fields.
- A trigger reads a parent field such as
Trigger.new[0].Account.Name. Trigger records include only the object’s own fields.
How to fix
- Add the named field to the query.
- In triggers, query related records separately using the IDs from
Trigger.new. - Keep queries in one selector class per object so every caller gets the same field set.
ListException: List index out of bounds
The code accessed a list position that does not exist, most often [0] on an empty list.
Common causes
- A query returned no rows and the code reads the first element.
- A
split()result has fewer parts than expected. - A loop counter runs past the end of the list.
How to fix
- Check
isEmpty()orsize()before accessing by index. - Use
for (Account a : accounts)loops, which cannot run out of bounds.
ListException: Duplicate id in list
A list passed to update or delete contains the same record more than once.
Common causes
- A loop over child records adds the same parent to the list once per child.
- Two code paths add the same record to a shared list.
How to fix
- Collect records in a map keyed by ID, then update the map’s values:
Map<Id, Account> toUpdate = new Map<Id, Account>();
for (Contact c : contacts) {
toUpdate.put(c.AccountId,
new Account(Id = c.AccountId, Has_Contacts__c = true));
}
update toUpdate.values();
CalloutException: You have uncommitted work pending
The code ran DML and then made a callout in the same transaction. Salesforce does not allow a callout while database changes are waiting to commit.
Common causes
- A record or log entry is inserted before the HTTP request.
- A callout is attempted directly from a trigger.
- A test method creates data and then calls code that makes a callout.
How to fix
- Reorder the code so all callouts happen before any DML.
- Move the callout to a
@future(callout=true)method or a Queueable that implementsDatabase.AllowsCallouts. - In tests, create data first, then call
Test.startTest(), set a mock withTest.setMock(), and run the code.
The code called a URL that is not registered in the org. The message includes the endpoint.
Common causes
- No Remote Site Setting or Named Credential exists for the URL.
- The registered URL differs in protocol, subdomain or port.
- The setting exists in sandbox but was not deployed to production.
- The sandbox and production integrations use different endpoints.
How to fix
- Add the URL in Setup → Remote Site Settings.
- Preferably use a Named Credential and call it as
callout:My_Credential/path. This keeps URLs and secrets out of code. - Include the Remote Site Setting or Named Credential in the deployment package.
CalloutException: Read timed out
The external service did not respond within the timeout. The default is 10 seconds and the maximum is 120 seconds.
Common causes
- The external service is slow or overloaded.
- The request asks for too much data at once.
- A firewall on the other side is not allowing Salesforce’s IP ranges.
How to fix
- Raise the timeout:
req.setTimeout(120000); - Request smaller pages of data.
- Move the callout to asynchronous Apex and add retry logic.
- Ask the external system’s owner to allow Salesforce IP ranges.
AsyncException: Future method cannot be called from a future or batch method
A @future method was invoked from code that is already running asynchronously.
Common causes
- A trigger calls a future method, and the trigger fired because a batch job or another future method updated the record.
How to fix
- Guard the call:
if (!System.isFuture() && !System.isBatch()) {
MyService.doWorkAsync(recordIds);
} else {
MyService.doWork(recordIds);
}
- Replace the future method with a Queueable job, which can be enqueued from asynchronous contexts.
Data conversion exceptions
These four exceptions come from bad input data or unsafe type handling.
| Exception | Typical cause | Fix |
|---|---|---|
StringException: Invalid id | A string that is not a valid ID is assigned to an Id variable | Validate with value instanceof Id before assigning |
TypeException: Invalid conversion from runtime type | A cast to the wrong type, such as casting a Contact to Account, or an Integer to Decimal after untyped JSON parsing | Check with instanceof or getSObjectType() before casting |
MathException: Divide by 0 | A denominator field is zero or blank | Check the denominator before dividing |
JSONException | The payload is malformed, has unexpected types, or is an HTML error page instead of JSON | Check res.getStatusCode() before parsing and log the raw body |
API and integration errors
These errors are returned to external systems calling Salesforce through REST, SOAP or Bulk API. Most are authentication, permission or query problems, and Login History and the integration user’s permissions resolve the majority.
In REST API the HTTP status narrows the cause before you read the error code.
| HTTP status | Meaning | Typical error codes |
|---|---|---|
| 400 | The request is invalid | MALFORMED_QUERY, INVALID_FIELD, INVALID_TYPE, plus all record save errors |
| 401 | Authentication failed | INVALID_SESSION_ID |
| 403 | Authenticated but not allowed | REQUEST_LIMIT_EXCEEDED, API_DISABLED_FOR_ORG |
| 404 | Resource not found | NOT_FOUND |
| 500 | Error inside Salesforce | UNKNOWN_EXCEPTION, QUERY_TIMEOUT |
| 503 | Service unavailable | SERVER_UNAVAILABLE |
INVALID_SESSION_ID
The access token or session ID is expired, revoked or not valid for the server it was sent to. The message reads Session expired or invalid.
Common causes
- The access token expired under the org’s session timeout setting.
- The request went to the wrong host, such as a token from a sandbox sent to production.
- The session was revoked, or the user logged out.
- The session is locked to the IP address it was created from and the request came from another.
- The
Authorizationheader is malformed, for example missing the wordBearer.
How to fix
- Use the refresh token to obtain a new access token, then retry the request once.
- Send requests to the
instance_urlreturned with the token, not a hardcoded host. - Check the header format:
Authorization: Bearer <token>. - Review Session Settings if tokens expire sooner than the integration expects.
INVALID_LOGIN
The username and password login was rejected. The message does not say which part was wrong.
Common causes
- Wrong username or password.
- The wrong login host. Sandboxes use
test.salesforce.comand sandbox usernames carry a suffix. - The security token is missing or out of date.
- The user is locked out, inactive or has an expired password.
- Profile login hours or login IP ranges block the attempt.
How to fix
- Open Setup → Login History and read the Status column for the exact reason.
- Confirm the login host matches the org type.
- Unlock the user or reset the password.
- Give integration users a password policy that does not expire, or move to an OAuth flow that needs no password.
LOGIN_MUST_USE_SECURITY_TOKEN
The login came from an IP address outside the org’s trusted ranges and no security token was supplied.
Common causes
- The integration server’s IP is not in Network Access.
- The security token was reset. This happens automatically on every password change.
How to fix
- Append the token directly to the password, with no space between them.
- Reset the token from the user’s personal settings. Salesforce emails the new one.
- Add the server’s IP address to Setup → Network Access or the profile’s login IP ranges.
- Move to an OAuth flow such as JWT bearer or client credentials, which avoids passwords and tokens.
REQUEST_LIMIT_EXCEEDED
The org has used its API allocation. TotalRequests Limit exceeded means the rolling 24-hour allocation is spent. ConcurrentRequests Limit exceeded means too many long-running requests are in progress at once.
Common causes
- An integration polls too often.
- Records are sent one per call rather than in batches.
- A retry loop without backoff floods the org after a failure.
- Several integrations share one org allocation and one of them spikes.
- Slow queries or slow automation keep requests open for a long time.
How to fix
- Check current usage in Setup → System Overview, and find the heaviest user with the “API Usage Last 7 Days” report.
- Batch records with the Composite or sObject Collections resources, which handle up to 200 records per call.
- Use Bulk API for large volumes.
- Replace polling with Change Data Capture or Platform Events.
- Add exponential backoff to retries.
- The 24-hour limit is rolling, so capacity returns gradually. Purchase additional API calls if usage is legitimately high.
API_DISABLED_FOR_ORG and API_CURRENTLY_DISABLED
API access is not available to this org or this user.
Common causes
- The Salesforce edition does not include API access.
- The user’s profile lacks the “API Enabled” permission.
- API access has been restricted at org level.
How to fix
- Grant “API Enabled” through a permission set.
- Confirm the edition includes API access, or add it through your account executive.
- If the permission is present and the error persists, raise a case with Salesforce Support.
MALFORMED_QUERY
The SOQL query has a syntax error. The message points to the position of the problem.
Common causes
- An unescaped single quote inside a value, such as
O'Brien. SELECT *, which SOQL does not support.- A date or datetime value wrapped in quotes.
- A missing or trailing comma, or unbalanced parentheses.
- The query string is not URL-encoded in a REST request.
How to fix
- Run the query in Developer Console’s Query Editor to isolate the syntax problem.
- Escape quotes with a backslash, or use
String.escapeSingleQuotes()in Apex. - Write dates without quotes:
CreatedDate > 2026-01-01T00:00:00Z. - List fields explicitly, and URL-encode the query in REST calls.
INVALID_FIELD
The request names a field Salesforce cannot find for this user. The message reads No such column 'Field__c' on entity 'Account'.
Common causes
- Field-level security hides the field from the integration user. This is the most common cause.
- A typo, or a missing
__csuffix or namespace prefix. - A relationship is referenced with
__cwhere__ris needed. - The field exists in sandbox but not in production.
How to fix
- Confirm the API name in Object Manager.
- Grant the integration user read or edit access to the field through a permission set.
- Use
__rto traverse custom relationships, for exampleParent__r.Name. - Deploy the field to the target org.
INVALID_TYPE
The request names an object Salesforce cannot find for this user. The message reads sObject type 'X' is not supported.
Common causes
- The user has no permission on the object.
- A typo, or a missing
__csuffix or namespace prefix. - The object or the feature that provides it is not enabled in this org.
How to fix
- Confirm the object’s API name.
- Grant object permissions through a permission set.
- Deploy the object or enable the feature in the target org.
NOT_FOUND
The URL does not resolve to a resource the user can access. The message reads The requested resource does not exist.
Common causes
- The record ID is wrong, or the record was deleted.
- The user has no access to the record, which looks the same as the record not existing.
- The URL has the wrong API version, object name or instance.
- A custom Apex REST path is wrong, or the user lacks access to the Apex class.
How to fix
- Check the ID exists by querying it as an administrator.
- Check the integration user’s sharing access to the record.
- Compare the URL against the pattern
/services/data/vXX.0/sobjects/Object/Id. - For Apex REST, grant the class to the user’s profile or permission set.
QUERY_TIMEOUT
A query ran too long and Salesforce cancelled it.
Common causes
- Filters on unindexed fields in a large object.
- Formula fields or cross-object fields in the
WHEREclause. - Sorting a very large result set.
How to fix
- Filter on indexed fields and narrow the date range.
- Split the extract into smaller ranges, or use Bulk API with PK chunking.
- Request a custom index from Salesforce Support for fields that are filtered often.
UNSUPPORTED_API_VERSION
The request uses an API version that Salesforce has retired or that does not exist.
How to fix
- Update the version in the endpoint URL to a current one.
- Check old integrations and middleware connectors, which often pin a version for years.
Salesforce could not process the request, usually because of maintenance or a service incident.
How to fix
- Check your instance on the Salesforce Trust status site.
- Retry with exponential backoff rather than immediately.
OAuth: invalid_grant
The token request was rejected. The error_description field gives the specific reason.
| Description | Cause | Fix |
|---|---|---|
authentication failure | Wrong credentials in the username-password flow, or that flow is blocked in the org | Check credentials and Login History. Prefer a different OAuth flow |
expired access/refresh token | The refresh token was revoked or expired under the app’s policy | Re-authorise the user. Review the refresh token policy on the app |
user hasn't approved this consumer | In the JWT flow, the user is not pre-authorised | Set the app to “Admin approved users are pre-authorized” and assign the user’s profile or permission set |
audience is invalid | The JWT aud claim is wrong | Use https://login.salesforce.com for production or https://test.salesforce.com for sandbox |
ip restricted | The request came from outside the allowed IP ranges | Relax IP restrictions on the app or add the IP range |
inactive user | The user is deactivated | Reactivate the user or switch the integration to an active one |
expired authorization code | The code was used twice or too late | Restart the flow and exchange the code immediately |
OAuth: invalid_client_id and invalid_client
Salesforce does not recognise the app’s consumer key or secret.
Common causes
- The consumer key or secret was copied incorrectly.
- The app was created minutes ago and has not finished propagating.
- The request goes to production while the app exists only in a sandbox, or the reverse.
How to fix
- Copy the key and secret again from the connected app or external client app.
- Wait about 10 minutes after creating or changing the app.
- Confirm the login host matches the org where the app is defined.
OAuth: redirect_uri_mismatch
The redirect_uri in the request does not exactly match a callback URL registered on the app.
Common causes
- A trailing slash,
httpinstead ofhttps, or a different port. - The URL is encoded differently from the registered value.
How to fix
- Copy the callback URL from the app settings and use it character for character.
- Register every environment’s callback URL on the app, one per line.
Flow and automation errors
Flow errors are usually a record save error or a missing value surfacing through a flow element. The flow error email and the Debug tool in Flow Builder show which element failed and why.
An unhandled fault has occurred in this flow
An element failed and the flow has no fault path to handle it. Users see this generic message. The real cause is in the flow error email.
Common causes
- A Create, Update or Delete Records element hit a record save error such as a validation rule.
- A Get Records element returned nothing and a later element used the empty result.
- The running user lacks permission on an object or field the flow touches.
- An Apex action called by the flow threw an exception.
- The flow hit a governor limit.
How to fix
- Read the flow error email. It lists each element that ran and the error on the last one.
- Open Setup → Paused and Failed Flow Interviews to inspect the failed run.
- Reproduce the failure with Debug in Flow Builder, using the same record and running as the affected user.
- Add a fault connector to every data element and action. Route it to a screen showing
{!$Flow.FaultMessage}, or to a logging step. - If the cause is user permissions and the flow should work for everyone, run it in system context.
The flow failed to access the value because it hasn’t been set or assigned
The flow read a variable or field that is null. This is the flow equivalent of a null pointer exception.
Common causes
- Get Records found no match and a later element references one of its fields.
- The flow reads through a blank lookup, for example
{!$Record.Account.Owner.Email}when the record has no account. - A variable expected as input was never passed in.
- A loop variable is referenced outside its loop.
How to fix
- Add a Decision after Get Records that checks the result is not null before continuing.
- Check a lookup field is populated before reading fields through it.
- Give variables default values where a sensible default exists.
- Tighten the flow’s entry conditions so it only runs when the fields it needs are filled.
FLOW_ELEMENT_ERROR
A specific flow element failed. The message names the operation and the underlying error, for example This error occurred when the flow tried to create records: REQUIRED_FIELD_MISSING.
Common causes
- Any record save error from the first section of this guide.
- The flow tries to update a record whose ID variable is empty. The message reads
The flow tried to update these records: null. - An ID from one object is used in a lookup to another.
How to fix
- Read the status code after the colon and look it up in the record save section.
- Check that record ID variables are populated before update and delete elements.
- Debug the flow and inspect the values going into the failing element.
CANNOT_EXECUTE_FLOW_TRIGGER
A record-triggered flow or legacy process failed during a save, so the whole save rolled back. Users see “We can’t save this record because the process failed.”
Common causes
- The flow failed for any of the reasons above.
- A field or record type the flow depends on was changed or deleted.
- A data load pushes records through a flow that was built for single-record edits.
How to fix
- The message names the flow. Open it and debug with the record that failed.
- Check the Setup Audit Trail for recent changes to fields the flow uses.
- For data loads, add a custom permission check to the entry conditions so the loading user bypasses the flow.
Governor limits in flows
Flows share the same transaction limits as Apex, so Too many SOQL queries: 101 and Too many DML statements: 151 appear in flow errors too.
Common causes
- Get, Create, Update or Delete Records elements inside a Loop.
- Several record-triggered flows on one object, each with its own data elements.
- A flow that updates a related object whose own flows then run in the same transaction.
How to fix
- Place Get Records before the loop.
- Inside the loop, use Assignment elements to change records and add them to a collection variable.
- After the loop, use one Update Records element on the collection.
- Use a before-save flow (Fast Field Updates) when updating the triggering record itself.
- Move heavy work to an asynchronous path so it runs in its own transaction.
- Consolidate multiple record-triggered flows on one object and set entry conditions so each runs only when needed.
User interface errors
These are the messages users report from Lightning Experience and Visualforce pages. Access problems account for most of them, and logging in as the affected user is the quickest way to reproduce one.
Insufficient Privileges
The user tried to open or act on something they have no access to. The full message reads “You do not have the level of access necessary to perform the operation you requested.”
Common causes
- The profile and permission sets give no access to the object.
- Sharing does not give the user access to this particular record.
- The record uses a record type the user is not assigned.
- The page depends on a Visualforce page or Apex class the user’s profile cannot access.
- The link points to a record in a different org, such as a sandbox link opened in production.
- A report, dashboard or list view sits in a folder not shared with the user.
How to fix
- Work through access in order: object permission, then record sharing, then field-level security, then Apex class or Visualforce page access.
- Check record access with a query:
SELECT RecordId, HasReadAccess, HasEditAccess, MaxAccessLevel
FROM UserRecordAccess
WHERE UserId = '005XXXXXXXXXXXX' AND RecordId = '001XXXXXXXXXXXX'
- Use the Sharing Hierarchy button on the record to see who has access and why.
- Grant missing access through a permission set, sharing rule or role change.
- Use Login As to confirm the fix as the affected user.
Sorry to interrupt: this page has an error
A Lightning component threw a JavaScript error. The details panel names the component and the error, for example Cannot read properties of undefined (reading 'Name').
Common causes
- Custom Aura or Lightning web component code reads data before it has loaded.
- An Apex method behind the component threw an exception the component did not handle.
- The user lacks access to the component’s Apex class or to a field it reads.
- A browser extension interferes with the page.
- The browser holds a stale cached version after a deployment.
- A managed package component has a defect or is misconfigured.
How to fix
- Record the component name and error text from the details panel.
- Refresh the page, then retry in a private window with extensions disabled.
- Check whether the error affects all users or only some. If only some, compare their permissions.
- Turn on Debug Mode for the user in Setup → Debug Mode to get a readable stack trace.
- In component code, guard against missing data:
get accountName() {
return this.record?.data?.fields?.Name?.value ?? '';
}
- Handle Apex errors in the component and show a toast instead of failing.
- For managed package components, send the error text to the vendor.
Maximum view state size limit (170KB) exceeded
A Visualforce page tried to store more than 170 KB of view state. View state holds the page’s component and controller data between requests.
Common causes
- The controller keeps large lists of records as member variables.
- Queries return more fields or rows than the page displays.
- Files or Blobs are held in controller variables.
- The page has a very large form or many nested components.
How to fix
- Mark variables
transientwhen they are only needed for the current request:
transient List<Account> searchResults;
- Query only the fields and rows the page shows, and add pagination.
- Use read-only output components for data that users do not edit.
- Enable “Show View State in Development Mode” on your user to see what is using the space.
- For heavy pages, move to JavaScript remoting or rebuild the page as a Lightning web component.
Data Not Available
The record the user tried to open cannot be found. The message reads “The data you were trying to access could not be found.”
Common causes
- The record was deleted.
- The ID in the URL belongs to another org, or the sandbox was refreshed.
- A custom link, button or formula builds the URL with a hardcoded or malformed ID.
- The record was merged into another.
How to fix
- Check the Recycle Bin and restore the record if needed.
- Confirm the user is in the right org.
- Review custom links and buttons that produce the URL and replace hardcoded IDs with merge fields.
- Search for the surviving record if a merge took place.
Deployment errors
Deployment errors come from differences between the source and target orgs: missing components, different data rules, or insufficient tests. Validating a deployment before releasing it surfaces all of them without changing the target org.
Code coverage below 75%
Production deployments that include Apex require at least 75% test coverage, and all tests must pass. The message reads Average test coverage across all Apex Classes and Triggers is X%, at least 75% test coverage is required.
Common causes
- New classes or triggers were deployed without tests.
- Tests fail in the target org, so the lines they would cover count as uncovered.
- Coverage looked sufficient in sandbox but production has more untested legacy code.
- A trigger in the deployment has no coverage at all.
- The deployment runs specified tests only, and those tests do not cover every class in the package.
How to fix
- Run all tests in a sandbox and open the coverage view in Developer Console to find uncovered lines.
- Write tests for the uncovered classes. Cover the bulk case and the error paths, with assertions.
- Fix failing tests first, since each failure reduces coverage.
- Remove dead code that no test can reach.
- When running specified tests, include the test classes that cover each component being deployed.
Test failures in the target org
Tests that pass in the source org fail during deployment.
Common causes
- Production has validation rules, required fields or duplicate rules that the sandbox lacks.
- Tests rely on existing org data, or use
SeeAllData=true. - Tests contain hardcoded IDs, usernames or profile names.
- Active flows or triggers in production change the outcome.
- Tests depend on the current date or time zone.
- Parallel test runs cause
UNABLE_TO_LOCK_ROW.
How to fix
- Read each failure message. Most are record save errors covered earlier in this guide.
- Build test data through a shared factory class so one fix updates every test.
- Create all data inside the test and avoid
SeeAllData=true. - Look up record types, profiles and users by name instead of ID.
- Refresh the sandbox, or deploy production’s configuration to it, so both match.
- Disable parallel Apex testing in Setup → Apex Test Execution → Options if lock errors appear.
No CustomField named X found
A component in the deployment references something that is not in the package or the target org. Variants name a CustomObject, RecordType, ApexClass or other component type.
Common causes
- A new field is used by a layout, flow or class in the package but was left out of it.
- A profile or permission set in the package references fields that do not exist in the target.
- Components are deployed in the wrong order across several packages.
- The component was renamed or deleted in the source after other components referenced it.
How to fix
- Add the missing component to the package.
- Deploy dependencies first when splitting work across several deployments.
- Remove stale references from profile and permission set files.
- Compare the two orgs with a diff tool to catch missing components before deploying.
Variable does not exist and Invalid type
Apex in the deployment fails to compile in the target org.
Common causes
- The code references a field, object or class that is not in the target.
- A managed package the code depends on is missing or is an older version.
- A field was deleted or renamed in the target org.
How to fix
- Include the missing field, object or class in the deployment.
- Install the same managed package version in the target org.
- Check the exact API name, including any namespace prefix.
Dependent class is invalid and needs recompilation
A class in the deployment depends on another class that no longer compiles. The message nests the underlying error beneath it.
Common causes
- A method signature changed and a calling class was not updated.
- A field or object the dependent class uses was removed.
- Only one of two related classes was included in the deployment.
How to fix
- Read to the last line of the message. It gives the real compile error and the class it is in.
- Deploy the changed class and its dependents together.
- In the target org, use Setup → Apex Classes → Compile all classes to reveal every broken class.
Cannot change field type, and component is referenced elsewhere
A field type change or a deletion is blocked because other components depend on the item.
Common causes
- The field is referenced in Apex, a flow, a formula, a validation rule or a report type.
- The requested type conversion is not supported, or would lose data.
- A deletion is attempted before the referencing components are updated.
How to fix
- Use the “Where is this used?” button on the field to list its references.
- Remove or update the references first, then make the change.
- For an unsupported type change, create a new field, migrate the data, repoint the references, then delete the old field.
- When deleting through a deployment, deploy the updated referencing components before the destructive change.
Sources
Limit figures were checked against Salesforce documentation on 2 October 2026. Causes and fixes for individual errors reflect general platform behaviour and common practice, so confirm details against your own org’s configuration.
- Execution Governors and Limits, Apex Developer Guide, Winter ’27
- Testing and Code Coverage, Apex Developer Guide, Winter ’27
- Optimize the View State, Visualforce Developer Guide