Querying the database using SOQL (Salesforce Object Query Language) is a fundamental part of Apex development. However, queries can fail at runtime due to malformed syntax, unhandled cardinality mismatches, or governor limit violations. Understanding these exceptions and applying defensive coding patterns ensures your application runs reliably under heavy enterprise workloads.
1. Common Query Exceptions in Salesforce Apex
Salesforce triggers several distinct exception types during query execution:
System.QueryException: Thrown when dynamic SOQL syntax is invalid, when referencing non-existent fields or objects, or when assigning a SOQL result directly to a single sObject variable when the query returns zero records or more than one record.System.LimitException: Too many SOQL queries: 101: Occurs when a transaction exceeds the synchronous limit of 100 SOQL queries (or 200 in asynchronous context), usually caused by querying inside loops.System.QueryException: Non-selective query against large table: Thrown when querying standard or custom objects with over 200,000 records without selective filters on indexed fields.System.LimitException: Too many query rows: 50001: Triggered when a single transaction retrieves more than 50,000 total sObject records across all queries.
- Synchronous Query Count: 100 SOQL queries per transaction.
- Total Retrieved Rows Limit: 50,000 rows per transaction.
- Heap Size Safeguard: 6 MB (Synchronous) / 12 MB (Asynchronous).
- Defensive Pattern: Always assign query results to a
List<sObject>rather than a single sObject variable.
2. Defensive Query Patterns and Error Handling
The most frequent QueryException occurs when developers expect exactly one record and write code like Account acc = [SELECT Id FROM Account WHERE Name = :inputName];. If no match is found, or if duplicate accounts exist, Salesforce immediately throws a fatal exception.
public with sharing class AccountQueryService {
/**
* @description Safely queries an Account by name without throwing runtime QueryExceptions
*/
public static Account getAccountSafely(String accountName) {
if (String.isBlank(accountName)) {
return null;
}
// 1. Always assign to a List to handle 0, 1, or multiple records safely
List<Account> accounts = [
SELECT Id, Name, Industry, AnnualRevenue
FROM Account
WHERE Name = :accountName.trim()
WITH USER_MODE
LIMIT 1
];
// 2. Safely evaluate list size rather than risking a single-record QueryException
return !accounts.isEmpty() ? accounts[0] : null;
}
/**
* @description Dynamic query execution wrapped in structured try-catch blocks
*/
public static List<sObject> executeDynamicQuery(String queryString) {
try {
return Database.query(queryString, AccessLevel.USER_MODE);
} catch (QueryException qe) {
System.debug(LoggingLevel.ERROR, 'SOQL Syntax or Runtime Error: ' + qe.getMessage());
// Rethrow custom application exception or log to persistent error framework
throw new AuraHandledException('Invalid query parameter provided.');
}
}
}
3. Core Strategies to Prevent Query Exceptions
- Bulkify Data Queries: Collect record IDs in
Set<Id>collections and issue a single query using theWHERE Id IN :idSetoperator outside loops. - SOQL For-Loops for Large Volumes: When processing thousands of records, iterate over queries in batches of 200 using
for (List<Account> chunk : [SELECT Id FROM Account])to prevent heap size exhaustion. - Ensure Query Selectivity: Filter by indexed fields (such as
Id,Name,CreatedDate, External IDs, or Lookup fields) to avoid timeout exceptions on large data volumes. - Proactive Limits Monitoring: Use
Limits.getQueries()andLimits.getLimitQueries()to inspect remaining headroom before executing dynamic queries in complex transactions.
4. Common Traps & Anti-Patterns
Unlike standard
QueryException errors, a System.LimitException (such as exceeding 100 SOQL queries or 50,000 rows) cannot be caught using a standard try-catch block. The transaction terminates immediately and rolls back all uncommitted work. You must design code defensively using bulkification rather than relying on catch blocks.
Summary
Preventing query exceptions in Salesforce Apex requires disciplined architectural habits: avoiding single-record query assignments, ensuring query selectivity, bulkifying triggers, and strictly avoiding queries inside loops. By writing defensive SOQL and monitoring runtime limits, developers can ensure enterprise-grade reliability and performance across their Salesforce orgs.