System.SObjectException: SObject row was retrieved via SOQL without querying the requested field
You read a field on a queried SObject that wasn't included in the SELECT clause. Apex stores SObjects sparsely — a field absent from SELECT throws this exception when accessed, even if it has a value in the database.
Also seen asSObject row was retrieved via SOQL without querying the requested field·SObjectException: SObject row was retrieved·without querying the requested field
Apex doesn't lazily load fields. If you query SELECT Id, Name FROM Account and then access account.Description, the runtime knows you didn't ask for Description and refuses to lie about its value (returning null would be misleading because the field might be populated). It throws this exception instead.
The fix
Add the field to the SELECT clause.
// Wrong
Account a = [SELECT Id, Name FROM Account WHERE Id = :accountId];
String description = a.Description; // SObjectException
// Right
Account a = [SELECT Id, Name, Description FROM Account WHERE Id = :accountId];
String description = a.Description;
Easy when you're writing the query and the use site in the same place. Painful when the query lives in a query helper class and the use site is two layers away.
The relationship-traversal version
The same rule applies to parent and child fields:
// Wrong: didn't ask for the parent's Name
Contact c = [SELECT Id, FirstName FROM Contact WHERE Id = :contactId];
String accountName = c.Account.Name; // SObjectException
// Right
Contact c = [SELECT Id, FirstName, Account.Name FROM Contact WHERE Id = :contactId];
String accountName = c.Account.Name;
For child relationships, the pattern is a subquery:
Account a = [
SELECT Id, Name,
(SELECT Id, FirstName, LastName FROM Contacts)
FROM Account WHERE Id = :accountId
];
for (Contact kid : a.Contacts) { ... }
When the error message names a field you did query
Two specific causes:
- You queried it on one variant of the same SObject and accessed it on another. A
Map<Id, Account>populated by one query, indexed by ID, but the variable you read from is a different reference that came from a different query. The platform tracks "which fields were queried" per-record, not per-class. - You're inside
Trigger.newreading a field added by a workflow. The platform sometimes runs your trigger before all field updates have populated the in-memory record. Re-query the record at the start of the trigger if you need fields that may have been touched by other automations.
A defensive pattern
For utility classes that get records handed to them, document the expected SELECT shape or re-query inside the utility:
public static void process(Set<Id> accountIds) {
Map<Id, Account> rich = new Map<Id, Account>([
SELECT Id, Name, Description, Annual_Revenue__c, Owner.Email
FROM Account WHERE Id IN :accountIds
]);
// ... use rich.get(...).Description safely
}
The cost of one extra query is small; the cost of a SObjectException thrown three weeks later in production is large.
Why the message says "via SOQL"
Records constructed with new Account(Name = 'Foo') (not from a query) are fully assignable — you can set any field, you can read any field, no exception. The error specifically mentions "via SOQL" because the platform tracks the field-set bitmap on records that came from queries.
