Skip to main content

Common Apex Errors in Salesforce: How to Fix Null Pointers, SOQL 101

In plain words: Apex errors occur when your code encounters an unexpected situation—like reading an unassigned variable (Null Pointer), requesting a record that does not exist (Query Exception), violating validation rules (DML Exception), or hitting multi-tenant resource caps (Governor Limits).

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.Name when AccountId is null).
  • Modern Resolution: Use the Safe Navigation Operator (?.) to short-circuit null evaluations safely.
Code Fix: Safe Navigation vs. Manual Checks
// ❌ 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. Use Database.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 @future or Queueable Apex.
Real-World Example: Defensive DML & Error Recovery
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.

360 Governor Limits Reference Card:
  • 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.
Developer Trap: SOQL and DML Inside Loops
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 generic Exception catch block.
  • Surface User-Friendly Messages: In Lightning Web Components (LWC) controllers, rethrow caught errors as AuraHandledException so 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

Core Rule: Bulkify code to process 200 records at a time, query with 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_MODE and execute DML using as user or AccessLevel.USER_MODE to respect permissions natively.
  • Write Meaningful Unit Tests: Test with realistic bulk datasets (200+ records) and validate outcomes using the modern Assert.areEqual() and Assert.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.