System.QueryException: unexpected token: '<EOF>'
What does this error mean?
<EOF> stands for "End of File" — in this context it means Salesforce reached the end of the SOQL query string but was still expecting more tokens to complete the syntax. The query is incomplete. This error almost always comes from dynamic SOQL built by concatenating strings, where a missing space, typo, or incorrect conditional branch produces a truncated or malformed query.
Common Causes
1. Missing space between tokens
When concatenating string fragments without proper spacing: 'SELECT Id' + 'FROM Account' produces SELECT IdFROM Account — invalid SOQL that reaches EOF before a valid parse.
2. Conditional WHERE clause built incorrectly
Dynamic WHERE clauses that conditionally append filter fragments often produce partial strings like SELECT Id FROM Account WHERE — the WHERE keyword with no condition causes an unexpected EOF.
3. Empty string variable in bind expression
A dynamic field name or object name variable that resolves to an empty string leaves a gap in the query structure.
How to Fix It
Solution 1: Debug-print the query string before execution
Always log the complete dynamic query string before calling Database.query(). This shows you the exact malformed string.
String soql = 'SELECT Id FROM ' + objectName
+ ' WHERE ' + filterClause;
// ✅ Always print before executing dynamic SOQL
System.debug('Query: ' + soql);
List<SObject> results = Database.query(soql);
Solution 2: Guard against empty WHERE clauses
When dynamically building a WHERE clause, use a List<String> of conditions and join them. This prevents orphaned WHERE keywords.
List<String> conditions = new List<String>();
if (filterByActive) {{
conditions.add('IsActive__c = true');
}}
if (ownerId != null) {{
conditions.add('OwnerId = \' + ownerId + '\');
}}
String soql = 'SELECT Id, Name FROM Account';
if (!conditions.isEmpty()) {{
soql += ' WHERE ' + String.join(conditions, ' AND ');
}}
System.debug(soql); // inspect before running
List<SObject> results = Database.query(soql);
Pro Tip: Use the Developer Console → Execute Anonymous window to paste and test your dynamic query string directly. If it fails, you'll see the exact parse error immediately — much faster than deploying and reading debug logs.