Salesforce Apex executes in a strongly typed, multi-tenant cloud runtime. While this architecture guarantees system security and shared resource availability, it also enforces strict execution rules. Encountering exceptions is part of everyday development, but understanding why they happen and how to resolve them defensively makes your code resilient, scalable, and production-ready.
1. Null Pointer Exceptions (NPE) & Safe Navigation
A System.NullPointerException happens when your code attempts to access a field, invoke a method, or index a collection on a reference that points to null.
- Uninitialized Collections: Declaring a list or map without instantiating it (
List<Account> accs; accs.add(newAcc);). Always initialize collections eagerly (new List<Account>()). - Traversing Blank Lookups: Accessing relationship fields on records where the lookup is empty (e.g.,
contact.Account.NamewhenAccountIdis null). - Modern Resolution: Use the Safe Navigation Operator (
?.) to short-circuit null evaluations safely.
// ❌ Risky: Throws NullPointerException if Account or Owner is null
String email = con.Account.Owner.Email;
// Modern Fix: Safe Navigation Operator returns null instead of throwing an NPE
String safeEmail = con?.Account?.Owner?.Email;
// Safe Map Retrieval
Map<Id, Account> accMap = new Map<Id, Account>();
String industry = accMap.get(targetId)?.Industry;
2. Query Exceptions (SOQL Errors)
A System.QueryException occurs when a SOQL query fails to execute properly or violates cardinality assumptions in Apex.
- List Has No Rows for Assignment: Assigning a query directly to a single sObject variable (
Account acc = [SELECT Id FROM Account WHERE Name = 'Acme'];) when zero records match. - Non-Selective SOQL Queries: Querying large tables (over 200,000 records) without filtering on indexed standard or custom fields.
- Fix: Always query into a
List<sObject>instead of a single sObject variable to handle zero-row returns gracefully.
3. DML Exceptions & Database Operations
A System.DmlException is thrown when a record fails database persistence due to custom validation rules, required fields, duplicate rules, or trigger failures.
- All-or-Nothing vs. Partial Success: Standard DML (
insert records;) fails the entire batch if one record fails. UseDatabase.insert(records, false)for partial success handling. - Mixed DML Exception: Modifying setup objects (e.g.,
User,PermissionSetAssignment) and non-setup objects (e.g.,Account) in the same synchronous transaction. Resolve this by offloading one operation to@futureor Queueable Apex.
Enforcing User Mode permissions and capturing detailed DML error codes:
public with sharing class LeadConversionService {
public static void updateLeadsSafely(List<Lead> leadsToUpdate) {
if (leadsToUpdate == null || leadsToUpdate.isEmpty()) {
return;
}
// Partial processing with explicit USER_MODE security
Database.SaveResult[] results = Database.update(leadsToUpdate, false, AccessLevel.USER_MODE);
for (Integer i = 0; i < results.size(); i++) {
if (!results[i].isSuccess()) {
for (Database.Error err : results[i].getErrors()) {
System.debug(LoggingLevel.ERROR, 'Lead ID ' + leadsToUpdate[i].Id + ' failed: ' + err.getMessage());
}
}
}
}
}
4. Governor Limit Exceptions (SOQL 101 & CPU Limits)
Salesforce enforces governor limits to prevent transactions from consuming shared multi-tenant resources. Unlike standard exceptions, System.LimitException cannot be caught with a try-catch block; the transaction halts and rolls back immediately.
- SOQL Queries: 100 Synchronous / 200 Asynchronous.
- DML Statements: 150 statements affecting up to 10,000 records.
- CPU Time: 10,000 ms Synchronous / 60,000 ms Asynchronous.
- Heap Size: 6 MB Synchronous / 12 MB Asynchronous.
Placing queries or database operations inside
for loops quickly triggers System.LimitException: Too many SOQL queries: 101. Always collect IDs into a Set<Id>, query once outside the loop, and perform bulk DML on a List.
5. Structured Exception Handling & Debugging
Implement structured exception handling to catch known errors, protect data integrity with database Savepoints, and surface clear error messages.
- Order Specific Catches First: Place specific catch blocks (
DmlException,QueryException) above the genericExceptioncatch block. - Surface User-Friendly Messages: In Lightning Web Components (LWC) controllers, rethrow caught errors as
AuraHandledExceptionso users receive clear notifications rather than raw stack traces. - Trace Flow with Structured Logs: Filter Developer Console logs using the "Debug Only" checkbox and assign explicit log levels (e.g.,
System.debug(LoggingLevel.ERROR, ...)).
6. Core Best Practices to Avoid Apex Errors
WITH USER_MODE to respect security, and use the safe navigation operator (?.) to prevent null pointer crashes.
- Enforce Object & Field Security: Query using
WITH USER_MODEand execute DML usingas userorAccessLevel.USER_MODEto respect permissions natively. - Write Meaningful Unit Tests: Test with realistic bulk datasets (200+ records) and validate outcomes using the modern
Assert.areEqual()andAssert.isTrue()methods. - Offload Heavy Tasks: Use Queueable Apex or Batch Apex when business logic approaches CPU or heap size boundaries.
Summary
Handling errors in Salesforce Apex requires proactive, defensive coding. By using the safe navigation operator, querying into lists to prevent query exceptions, bulkifying database operations outside loops, and structuring try-catch blocks with clear logging, you build reliable applications that scale smoothly across the Salesforce platform.