Salesforce Dictionary - Free Salesforce GlossarySalesforce Dictionary
All errors
SOQL

INVALID_FIELD: NULL_FOR_NON_REFERENCE_FIELD: <field> can not be null

You assigned `null` to a field type that doesn't accept null — usually a Boolean (which expects `true` or `false`, not null) or a primitive Number on certain field configurations. The fix is supplying an explicit value instead of null.

Also seen asNULL_FOR_NON_REFERENCE_FIELD·field can not be null·NULL value for non-reference

A nightly integration imports CSV files into Salesforce. Wednesday's load fails on the very first batch with INVALID_FIELD: NULL_FOR_NON_REFERENCE_FIELD: AccountId can not be null. The team checks the staging file. The AccountId column is populated for every row. The SOQL statement looks the same as the one that ran clean for the last eight months. The integration job is paused while the developer searches for the cause.

What the platform is checking

NULL_FOR_NON_REFERENCE_FIELD is a SOQL filter error, not a DML error. The platform raises it when a query binds a null value into a filter clause that targets a reference field, an Id field, or any field that cannot accept null in a comparison. The query engine sees WHERE AccountId = null and refuses to execute. Comparing an Id column against null in this position is treated as an invalid expression rather than a meaningful filter.

The error is precise about the field name. The message also names the type of constraint that was violated. The cause is almost always a bind variable that resolved to null at runtime when the developer assumed it would always carry a value. SOQL does not silently coerce null bindings into "match everything" or "match nothing." It rejects the query before any rows are read.

The platform's reasoning is intentional. A null bind variable in a filter usually points at a bug upstream, where a value was supposed to be populated and was not. Letting the query run with AccountId = null would either return an empty set (silently hiding the bug) or return the wrong rows. The strict error surfaces the issue at the boundary.

The broken example

A controller that loads Opportunities for a chosen Account:

public class OpportunityListController {
    @AuraEnabled(cacheable=true)
    public static List<Opportunity> getOpportunities(Id accountId) {
        return [
            SELECT Id, Name, Amount, StageName
            FROM Opportunity
            WHERE AccountId = :accountId
            ORDER BY CloseDate DESC
            LIMIT 50
        ];
    }
}

The method works for every Account that has a populated Id. A Lightning page that invokes the controller before the user picks an Account passes accountId as null. The bind binds null into the filter. The query engine raises NULL_FOR_NON_REFERENCE_FIELD: AccountId can not be null and the page shows a red error toast.

A second shape: a batch job that filters Opportunities by Owner:

public Database.QueryLocator start(Database.BatchableContext bc) {
    Id ownerId = SomeService.getActiveOwner();
    return Database.getQueryLocator(
        'SELECT Id FROM Opportunity WHERE OwnerId = :ownerId AND IsClosed = false'
    );
}

If SomeService.getActiveOwner() returns null because no active owner is configured, the QueryLocator construction fails with the same error. The batch never starts.

A third shape: a dynamic SOQL builder that concatenates clauses:

String soql = 'SELECT Id FROM Case WHERE AccountId = ' + accountId;
List<Case> cases = Database.query(soql);

When accountId is null, the resulting SOQL string reads WHERE AccountId = null. The query engine treats this as a forbidden filter on a reference column and throws.

The fix, three paths

Guard the bind variable before the query. The simplest fix is an early return when the input cannot produce a meaningful query:

@AuraEnabled(cacheable=true)
public static List<Opportunity> getOpportunities(Id accountId) {
    if (accountId == null) {
        return new List<Opportunity>();
    }
    return [
        SELECT Id, Name, Amount, StageName
        FROM Opportunity
        WHERE AccountId = :accountId
        ORDER BY CloseDate DESC
        LIMIT 50
    ];
}

The caller receives an empty list. The UI shows the empty state. No query runs against a null filter.

Use the right operator for "is null" semantics. When the intent really is to find rows where the field is null, SOQL has a dedicated operator. WHERE AccountId = null is invalid for reference fields, but WHERE AccountId = null is valid for nullable scalar fields like text. The clean form is WHERE AccountId = null written as WHERE AccountId = null only when allowed; for Id and lookup fields, use the explicit form:

List<Opportunity> orphans = [
    SELECT Id FROM Opportunity WHERE AccountId = null
];

Salesforce allows the literal null in SOQL filters when the comparison is the recognized "is null" pattern. The error fires when a bind variable resolves to null. The literal form is unambiguous; the bind form is rejected.

Choose the right filter shape for the use case. A common pattern is to either filter by a specific value or to return all rows when no specific value is set. The clean expression of that intent uses two query paths or a dynamic SOQL builder that adjusts the WHERE clause:

String soql = 'SELECT Id, Name FROM Opportunity';
List<String> wheres = new List<String>();
List<Id> binds = new List<Id>();
if (accountId != null) {
    wheres.add('AccountId = :accountId');
    binds.add(accountId);
}
if (!wheres.isEmpty()) {
    soql += ' WHERE ' + String.join(wheres, ' AND ');
}
return Database.query(soql);

The WHERE clause only appears when there is a real value to filter on. The empty filter case returns all rows. The intent is explicit and the query engine never sees a null bind.

The fixed example

The original controller with input validation and a sensible empty result:

public class OpportunityListController {
    @AuraEnabled(cacheable=true)
    public static List<Opportunity> getOpportunities(Id accountId) {
        if (accountId == null) {
            return new List<Opportunity>();
        }
        return [
            SELECT Id, Name, Amount, StageName, CloseDate
            FROM Opportunity
            WHERE AccountId = :accountId
            ORDER BY CloseDate DESC
            LIMIT 50
        ];
    }
}

The method now contracts that a null input yields no results. Callers can rely on the empty list and render an empty state. The query never executes with a null bind.

Edge case: SOQL bind variables in dynamic queries

Dynamic SOQL via Database.query() accepts bind variables in the same form as static SOQL, but the bind resolution happens at runtime. A variable that was non-null at compile-time review may resolve to null in production. Always validate bind sources before building the SOQL string:

public static List<Account> searchAccounts(String name, Id industryId) {
    if (String.isBlank(name) && industryId == null) {
        return new List<Account>();
    }
    String soql = 'SELECT Id, Name FROM Account';
    List<String> wheres = new List<String>();
    if (String.isNotBlank(name)) {
        wheres.add('Name LIKE :name');
        name = '%' + name + '%';
    }
    if (industryId != null) {
        wheres.add('Industry_Id__c = :industryId');
    }
    soql += ' WHERE ' + String.join(wheres, ' AND ');
    return Database.query(soql);
}

Each bind is gated by a non-null check. The WHERE clause assembles only the conditions that have real values. No null binds reach the query engine.

Edge case: relationship-based filters

A filter that walks a relationship can resolve to null at the relationship endpoint:

[SELECT Id FROM Opportunity WHERE Account.OwnerId = :userId]

If userId is null, the same error fires. The fix is the same: guard the input before the query.

A subtler shape: filters that combine a relationship with an Id field:

[SELECT Id FROM Case WHERE Account.ParentId = :parentId]

If the Account has no parent, Account.ParentId is null on the rows themselves, which SOQL handles correctly. The error fires only when the bind variable :parentId is null, not when the field value being compared is null.

Edge case: SOSL versus SOQL

SOSL search statements behave differently. A FIND :searchTerm IN ALL FIELDS with a null searchTerm raises a different error (SearchException), not NULL_FOR_NON_REFERENCE_FIELD. SOQL and SOSL share the bind-variable syntax but enforce null differently. When migrating filters between the two, do not assume the same null-handling rules apply.

Edge case: IN clauses with collections that contain null

A SOQL WHERE Id IN :idCollection with a collection containing null elements behaves unpredictably. Some Apex versions accept the null and treat it as a no-match; others throw a similar error. The cleanest pattern is to scrub nulls from the collection before binding:

Set<Id> cleanIds = new Set<Id>();
for (Id i : possiblyMixedIds) {
    if (i != null) cleanIds.add(i);
}
List<Opportunity> opps = [
    SELECT Id FROM Opportunity WHERE Id IN :cleanIds
];

Removing nulls before the bind eliminates an entire class of subtle failures. The query intent stays the same; the binding shape is always well-formed.

Edge case: integrations from external systems

External systems that build SOQL strings and submit them via the REST or SOAP API are subject to the same rule. A middleware service that concatenates a customer id into a query string can produce WHERE AccountId = null when the input is missing. The fix is the same on the integration side: guard the input before constructing the SOQL.

def get_opportunities(account_id):
    if account_id is None:
        return []
    soql = f"SELECT Id, Name FROM Opportunity WHERE AccountId = '{account_id}'"
    return run_query(soql)

The guard sits in the integration code. The Salesforce side never receives a null-filter query.

Defensive habits

Treat every bind variable as potentially null when its source is user input, query string, or upstream service. A validation gate at the entry of any query-bearing method catches the issue once, near where it originated.

Prefer the empty result over the exception. When the input cannot produce a meaningful query, returning an empty collection lets the UI render its empty state without a stack trace. Callers handle empty lists more gracefully than they handle exceptions.

Build dynamic SOQL with explicit clause assembly, not string concatenation. Concatenation hides null binds inside the SOQL string; assembly forces every clause to pass a non-null check first.

Document the null contract for every public query method. If callers must pass non-null Ids, throw a clear IllegalArgumentException at the top of the method with a useful message. The caller then knows the contract and can fix the upstream cause.

Test patterns

A test that covers the null-input path:

@IsTest
static void getOpportunitiesReturnsEmptyForNullAccount() {
    Test.startTest();
    List<Opportunity> result = OpportunityListController.getOpportunities(null);
    Test.stopTest();
    System.assertEquals(0, result.size(), 'Null accountId should yield empty list');
}

@IsTest
static void getOpportunitiesReturnsOppsForValidAccount() {
    Account a = new Account(Name='Test');
    insert a;
    Opportunity o = new Opportunity(
        Name='Test Opp', AccountId=a.Id, StageName='Prospecting',
        CloseDate=Date.today().addDays(30), Amount=1000
    );
    insert o;
    Test.startTest();
    List<Opportunity> result = OpportunityListController.getOpportunities(a.Id);
    Test.stopTest();
    System.assertEquals(1, result.size());
}

Two tests cover the contract: one for the null input (empty result), one for the populated input (matching record). Together they pin the public behavior.

Diagnosing in production

When the error fires:

  1. Read the error message and identify the named field.
  2. Find the query in the code base. Grep for the field name in a WHERE clause.
  3. Identify the bind variable. Trace its source.
  4. Reproduce the null condition in a sandbox. Often a debug log around the query confirms which input was null.
  5. Add a guard at the entry of the method, return an empty result, or throw a clearer message.

The error message gives the field name but not the line of code. A search for WHERE <FieldName> in the repo usually surfaces the candidate queries within a few seconds.

Beyond the immediate fix

A query that fails because a bind is null is a symptom of a contract gap. The caller passed null when the method expected a value. The fix at the query is a workaround; the long-term fix is to make the upstream call site populate the input correctly or to mark the parameter clearly as optional.

For Lightning controllers backing record pages, the recordId parameter is reliably non-null when the user is on a record. Custom pages that load before a record is selected pass null. The controller should handle that gracefully.

For batch jobs and scheduled processes, the inputs come from configuration or from upstream queries. A configuration that is allowed to be null should be guarded; a configuration that should never be null should be validated at startup with a clear error if the team forgot to set it.

Quick recovery checklist

  1. Read the error and find the named field.
  2. Locate the query and identify the bind variable.
  3. Add a null guard at the calling method.
  4. Decide whether the null case should return empty, run a different query, or throw a useful exception.
  5. Add a regression test for the null input.
  6. Deploy and confirm the integration resumes.

Most occurrences resolve within minutes once the bind source is found. The longer fix is updating callers so the null never reaches the query in the first place.

Further reading from Salesforce

Related dictionary terms

Share this fix

Share on LinkedInShare on X

Related SOQL errors