Initialise collections by default, guard lookups before dereferencing, and choose empty-return patterns over null returns so asynchronous and trigger workflows do not fail at runtime.
Why do Apex collection workflows fail with Null Pointer Exceptions?
Most collection-related Null Pointer Exceptions happen because code assumes a Map, List, or lookup result always exists when runtime data says otherwise.
In production, this shows up when one of these conditions appears:
- A map was declared but never initialised before
get()orput(). - A key lookup returns
null, then code immediately dereferences it. - A helper method returns
nullinstead of an empty collection. - Async jobs process partial data where expected parent rows are missing.
What is the safest default for Apex collection variables?
Use initialise-first defaults and return empty collections, not null.
public with sharing class InvoiceService {
public static Map<Id, Decimal> loadInvoiceTotals(Set<Id> accountIds) {
Map<Id, Decimal> totalsByAccount = new Map<Id, Decimal>();
if (accountIds == null || accountIds.isEmpty()) {
return totalsByAccount;
}
for (AggregateResult row : [
SELECT Account__c accountId, SUM(Total__c) amount
FROM Invoice__c
WHERE Account__c IN :accountIds
GROUP BY Account__c
]) {
totalsByAccount.put(
(Id) row.get('accountId'),
(Decimal) row.get('amount')
);
}
return totalsByAccount;
}
}
This pattern avoids null checks across every caller and keeps flow predictable.
Decision and Trade-offs
| Pattern | Benefit | Risk / Cost |
|---|---|---|
Return null for “no data” |
Slightly less memory allocation | Every caller must defend against null; easy to miss in async/trigger paths |
| Return empty collection (recommended) | Predictable API contract and fewer runtime failures | Minor allocation cost |
| Null-check only at controller boundary | Cleaner service internals | Hidden failures when reused from jobs, triggers, and queueables |
| Defensive checks at every dereference | Strong runtime safety | More verbose code; needs consistent team standards |
For Salesforce workloads, reliability and predictable behavior usually matter more than micro-optimising allocations.
Which code paths are most likely to fail in real orgs?
1) Lookup then dereference
Opportunity opp = oppById.get(oppId);
// Fails when opp is null
if (opp.StageName == 'Closed Won') {
// ...
}
Safer version:
Opportunity opp = oppById.get(oppId);
if (opp != null && opp.StageName == 'Closed Won') {
// ...
}
2) Nested map access without intermediate guards
// Fails if accountToContacts.get(accountId) returns null
Contact primary = accountToContacts.get(accountId)[0];
Safer version:
List<Contact> contacts = accountToContacts.get(accountId);
if (contacts != null && !contacts.isEmpty()) {
Contact primary = contacts[0];
}
3) Uninitialised write target collection
List<Task> followUps;
followUps.add(new Task(Subject = 'Call customer'));
Safer version:
List<Task> followUps = new List<Task>();
followUps.add(new Task(Subject = 'Call customer'));
How should you design helper method contracts?
Define explicit contracts so callers know exactly what to expect:
- Collection-returning methods return empty collections, never
null. - Single-record methods may return
null, but caller must guard before dereference. - Methods that require non-null params should fail fast with meaningful exceptions.
public static List<Case> getOpenCasesByAccount(Id accountId) {
if (accountId == null) {
throw new IllegalArgumentException('accountId is required');
}
List<Case> rows = [
SELECT Id, Subject, Status
FROM Case
WHERE AccountId = :accountId
AND IsClosed = false
];
return rows == null ? new List<Case>() : rows;
}
What changes when processing large data volumes?
Null pointer risk increases with scale because data quality variance increases.
At higher volumes:
- Related records are more likely to be missing in the same transaction.
- Async chunks can process incomplete data windows.
- Partial retries can re-enter code paths with different state.
Scale-safe practices:
- Treat every map lookup as optional unless guaranteed by query design.
- Prefer guard clauses early in loops.
- Log key IDs when null conditions are unexpected.
- Separate data loading from business logic to make assumptions obvious.
Debugging checklist for Null Pointer Exceptions in Apex
When this error appears in logs, run this sequence first:
- Identify the exact dereference line from the stack trace.
- Log the variable state immediately before that line.
- Check whether the source collection was initialised and populated.
- Validate query assumptions (missing parent/child rows, filter drift).
- Confirm helper methods return empty collections rather than
null. - Reproduce using the same profile/record shape as the failing transaction.
Testing strategy that catches these failures earlier
Write tests for both happy path and sparse-data path.
@isTest
private class InvoiceServiceTest {
@isTest
static void returnsEmptyMapWhenInputEmpty() {
Map<Id, Decimal> totals = InvoiceService.loadInvoiceTotals(new Set<Id>());
System.assertNotEquals(null, totals);
System.assertEquals(0, totals.size());
}
@isTest
static void noNullPointerWhenAccountHasNoInvoices() {
Account a = new Account(Name = 'No Invoice Account');
insert a;
Test.startTest();
Map<Id, Decimal> totals = InvoiceService.loadInvoiceTotals(new Set<Id>{a.Id});
Test.stopTest();
System.assertNotEquals(null, totals);
System.assertEquals(false, totals.containsKey(a.Id));
}
}
If your tests only assert success with complete fixture data, you will miss the production failure path.
Conclusion
Null Pointer Exceptions in Apex collection workflows are usually contract problems, not language problems.
Use these defaults to reduce failures quickly:
- Initialise collections early.
- Return empty collections from collection-returning methods.
- Guard map/list lookups before dereferencing.
- Test sparse and partial-data scenarios, not only ideal data.
If you are seeing recurring collection-related failures in triggers, queueables, or batch jobs, fix method contracts first. That gives the fastest reliability gain with the least architectural churn.
Need help stabilising Apex failures in production?
I take short specialist contracts to diagnose recurring Apex failures and harden collection-heavy workflows.