Initial term of field expression must be a concrete SObject: <type>
You wrote `someThing.Field__c` where `someThing` isn't a specific SObject type — usually an `SObject` or `Object` reference. Apex needs the concrete type at compile time to resolve the field name. Cast to the specific SObject before accessing fields.
Also seen asInitial term of field expression must be a concrete SObject·must be a concrete SObject·concrete SObject apex
Apex's strong typing applies to field access: record.Name works only if Apex knows record is, say, Account (which has a Name). If record is the abstract SObject type or Object, the compiler can't resolve Name and throws this error.
The shape of the bug
SObject record = [SELECT Id, Name FROM Account LIMIT 1][0];
String name = record.Name; // ❌ Initial term of field expression must be a concrete SObject
The runtime knows it's actually an Account, but the compiler sees SObject and refuses.
Fix 1: cast to the specific type
SObject record = [SELECT Id, Name FROM Account LIMIT 1][0];
Account a = (Account) record;
String name = a.Name;
Or inline:
String name = ((Account) record).Name;
The cast satisfies the compiler. It fails at runtime if the actual record isn't an Account.
Fix 2: use SObject's get() method
For genuinely-untyped code (e.g., a trigger handler that processes any sObject), use the dictionary-style accessor:
SObject record = ...;
String name = (String) record.get('Name');
get() returns Object, which you cast to the right type. Slower and less safe than direct field access, but works without compile-time knowledge of the type.
Fix 3: query into the typed list
If you wrote SObject because the variable was a list:
List<SObject> records = Database.query('SELECT Id, Name FROM Account'); // ❌
for (SObject r : records) { String n = r.Name; } // boom
If the source is a hard-coded type, use the typed list:
List<Account> records = [SELECT Id, Name FROM Account];
for (Account r : records) { String n = r.Name; } // ✅
For dynamic SOQL where the type is computed:
List<SObject> records = Database.query(soqlString);
for (SObject r : records) {
Object name = r.get('Name');
}
A subtle case: relationship traversal
Account a = [SELECT Id, Owner.Email FROM Account LIMIT 1];
String email = a.Owner.Email; // works if Owner has Email
But if Owner is polymorphic (could be User OR Group), the compiler can't pick one statically:
String email = a.Owner.Email; // sometimes errors — Owner could be Group, which has no Email
Use TYPEOF in SOQL to disambiguate:
[SELECT Id, TYPEOF Owner WHEN User THEN Email END FROM Account]
Or cast at runtime:
if (a.Owner instanceof User) {
String email = ((User) a.Owner).Email;
}
When the variable IS a concrete SObject
Three suspects if the message looks wrong:
- The variable was declared as
SObjectsomewhere upstream and re-assigned - The method's parameter type is
SObjecteven though you always pass Accounts - A generic helper class returns
SObjectfor portability
Cast at the call site or refactor the helper to return the specific type when known.
