MALFORMED_QUERY: unexpected token
The SOQL parser couldn't make sense of your query. The error message tells you the exact word it choked on — that's where the syntax broke. Most often it's a quoting problem, an unsupported keyword, or a relationship name typo.
Also seen asMALFORMED_QUERY·unexpected token·MALFORMED_QUERY: unexpected token·INVALID_QUERY_LOCATOR malformed
SOQL looks like SQL but is much stricter. The parser reports the first unexpected token, and that's almost always either right where you wrote a typo or one token after the actual problem.
Read it like a stack trace
MALFORMED_QUERY:
SELECT Id, Name FROM Account WHERE Name = 'O'Brien'
^
unexpected token: 'Brien'
The parser sees the closing quote, considers the string literal done at 'O', and now Brien' is just a stray identifier. The fix is escaping the apostrophe by doubling it: 'O''Brien'. In Apex code, you'd write the literal as 'O\'Brien' — backslash for the embedded source-level escape, and the bind variable handles the double-up for you automatically:
String name = 'O\'Brien';
List<Account> hits = [SELECT Id, Name FROM Account WHERE Name = :name];
Always prefer bind variables (:variable) over string concatenation. The platform escapes them correctly and they keep you safe from SOQL injection.
The other classic offenders
- Reserved words used as identifiers. Field names like
Group,User, orDateneed to be qualified or quoted with the table alias. - Cross-object queries with a typo. Querying
SELECT Account.Name FROM Contactworks;SELECT Acccount.Name(extra c) gives youunexpected token: Acccount. - Child relationship name confusion. Subqueries use the relationship name, not the child object's API name:
Custom objects useSELECT Id, (SELECT Id FROM Contacts) FROM Account -- right (relationship 'Contacts') SELECT Id, (SELECT Id FROM Contact) FROM Account -- wrong (object name)__rfor the relationship:SELECT Id, (SELECT Id FROM Cases__r) FROM Account__c. - Mixing single-row and aggregate selects.
SELECT COUNT(Id), Id FROM Accountdoesn't work; aggregate queries can only mix non-aggregated fields if they're inGROUP BY. LIKEwith no wildcard. Allowed by the parser but probably not what you meant. The error usually shows up later as zero results.
Reproduce it cleanly
Don't debug SOQL inside an Apex method. Open Setup → Developer Console → Query Editor and paste the literal query string. The Query Editor's error messages are clearer and you can iterate fast. Once it parses, port it back to Apex and replace literals with bind variables.
When the parser is right but it feels wrong
If you're sure the query looks valid, two situations to suspect:
- A field that doesn't exist on this profile / permission set (you'd actually get
INVALID_FIELDfor that, notMALFORMED_QUERY— see INVALID_FIELD: No such column). - A query string built by string concatenation where a variable resolved to the empty string.
'WHERE Status = \'' + maybeNull + '\''becomesWHERE Status = ''with a stray quote. Bind variables avoid this.
