INVALID_SEARCH: search term must be longer than one character
A SOSL `FIND` expression had a search term shorter than two characters, or used wildcards in a way the parser rejects. SOSL has stricter rules than regular full-text search engines — single-letter searches and leading wildcards are blocked.
Also seen asINVALID_SEARCH·search term must be longer than one character·SOSL search must include identifier·search query too short
A support agent uses a Lightning quick-find utility built on top of SOSL to search across Cases, Accounts, and Contacts in one query. The utility has a text input and runs the search as the agent types. When the agent types a single letter, the toast appears: INVALID_SEARCH: search term must be longer than one character. The build worked in unit tests because the test fixtures always passed a fully formed query. The developer needs to handle the input-validation boundary that SOSL enforces.
What the platform is checking
SOSL (Salesforce Object Search Language) is the search engine behind global search, Search Layouts, and programmatic full-text search. SOSL queries against an inverted index of record content. The platform enforces a minimum search term length to keep the index efficient and to prevent queries that would scan the entire index.
The minimum term length is two characters by default for most words. Specific data types have their own minima. Standard identifiers (id values, named keys) bypass the minimum when wrapped in quotes. Asian-language characters (CJK) sometimes have different minima because index segmentation works per character rather than per word.
When a SOSL query's FIND clause contains only single-character terms, the platform refuses to run the search and returns INVALID_SEARCH: search term must be longer than one character. The error fires at parse time, before any record is scanned. The query never reaches the index.
The same restriction applies to wildcard searches with very short prefixes. FIND {a*} is rejected because the platform won't enumerate every record whose indexed terms start with "a". The wildcard makes the search broad enough that the platform requires more specificity from the caller.
The broken example
A controller that builds a SOSL query from user input:
public class QuickFindController {
@AuraEnabled(cacheable=false)
public static List<List<SObject>> search(String term) {
String query = 'FIND {' + term + '} IN ALL FIELDS RETURNING ' +
'Account(Id, Name), Contact(Id, Name, Email), Case(Id, CaseNumber, Subject)';
return Search.query(query);
}
}
The method takes the raw input from the UI and embeds it into the SOSL string. When the agent types a single letter, the query becomes FIND {a} ... and the platform rejects it.
A second shape: the search builds correctly for multi-character input but breaks for the wildcard suffix.
String query = 'FIND {' + term + '*} IN ALL FIELDS RETURNING ...';
If term is empty (the user cleared the input but the search ran anyway), the query becomes FIND {*}, which is also rejected. If term is a single character, FIND {a*} is also rejected because the wildcard is too greedy.
A third shape: a SOSL injection vulnerability where the user input contains SOSL syntax that breaks the query. A user typing { or } truncates the FIND clause early and the platform throws a different error, but the same defensive layer applies: validate the input before assembling the query.
What counts as a valid search term
The platform's search index parses field content into tokens. Each token is a unit that can be searched. Whitespace, punctuation, and certain control characters separate tokens. The minimum-length rule applies to the search term as the platform sees it after tokenization.
For example, searching for "O'Reilly" parses as two tokens in some configurations: O and Reilly. The single-character O would normally fail the minimum, but quoted searches treat the entire string as a phrase and bypass the per-token check. Searching for O'Reilly without quotes can produce the error if the tokenizer splits on the apostrophe.
Numbers count as tokens. 2026 is a valid search term because it has four characters. 5 is rejected. Currency symbols and units are typically stripped during indexing.
Wildcards * (zero or more characters) and ? (single character) are allowed but require at least one literal character before them. * alone is rejected. a* is rejected because the literal portion is one character. ab* is accepted because the literal portion is two characters.
The fix, three paths
Validate the input length before building the query. The cleanest fix is to short-circuit the search when the input is too short.
@AuraEnabled(cacheable=false)
public static List<List<SObject>> search(String term) {
if (term == null || term.length() < 2) {
return new List<List<SObject>>{
new List<SObject>(),
new List<SObject>(),
new List<SObject>()
};
}
String query = 'FIND :term IN ALL FIELDS RETURNING ' +
'Account(Id, Name), Contact(Id, Name, Email), Case(Id, CaseNumber, Subject)';
return Search.query(query);
}
The method returns empty lists for short terms. The UI displays "type more characters" instead of an error toast. The user experience improves and the platform never sees an invalid query.
Use bound parameters to avoid string concatenation. The bound-parameter form FIND :term lets the platform handle the term safely, including character escaping. Bound parameters prevent SOSL injection.
String term = userInput;
List<List<SObject>> results = [
FIND :term IN NAME FIELDS
RETURNING Account(Id, Name), Contact(Id, Name, Email)
];
The parameter is passed safely. The single-character validation must still happen at the application layer because the bound parameter does not bypass the platform's minimum.
Throttle the search to fire only when input is meaningful. Beyond a strict minimum, debounce the input so search runs only after the user pauses typing. Even when the input is two characters long, firing a search on every keystroke wastes API calls and gives noisy results.
import { LightningElement, track } from 'lwc';
import search from '@salesforce/apex/QuickFindController.search';
export default class QuickFind extends LightningElement {
@track results = [];
searchTimeout;
handleInput(event) {
clearTimeout(this.searchTimeout);
const term = event.target.value;
if (!term || term.length < 2) {
this.results = [];
return;
}
this.searchTimeout = setTimeout(async () => {
this.results = await search({ term });
}, 300);
}
}
The debounce waits 300 milliseconds after the last keystroke before firing. Short inputs are filtered out client-side.
The fixed example
A complete quick-find controller with validation, parameterization, and a sensible result shape:
public with sharing class QuickFindController {
public class SearchResults {
@AuraEnabled public List<Account> accounts;
@AuraEnabled public List<Contact> contacts;
@AuraEnabled public List<Case> cases;
public SearchResults() {
this.accounts = new List<Account>();
this.contacts = new List<Contact>();
this.cases = new List<Case>();
}
}
@AuraEnabled(cacheable=false)
public static SearchResults search(String term) {
SearchResults results = new SearchResults();
if (String.isBlank(term) || term.length() < 2) {
return results;
}
List<List<SObject>> raw = [
FIND :term IN NAME FIELDS
RETURNING
Account(Id, Name LIMIT 20),
Contact(Id, Name, Email LIMIT 20),
Case(Id, CaseNumber, Subject LIMIT 20)
];
results.accounts = (List<Account>) raw[0];
results.contacts = (List<Contact>) raw[1];
results.cases = (List<Case>) raw[2];
return results;
}
}
The method validates the input. It uses bound parameters to prevent injection. It limits each object's results to 20 to keep payloads small. It returns a structured result so the LWC can render each section independently.
Edge case: very common terms produce poor results even when valid
A search for "the" returns thousands of results because the term appears in almost every record. The platform does not reject the query, but the result set is noisy and slow to render. Application-level rules should reject overly common terms (a stop-word list) before sending them to SOSL.
private static Set<String> STOP_WORDS = new Set<String>{'the', 'and', 'or', 'a', 'an'};
if (STOP_WORDS.contains(term.toLowerCase())) {
return results;
}
The fix is policy, not platform enforcement. The platform does not consider any term a stop word automatically.
Edge case: international characters and segmentation
For users typing in Chinese, Japanese, or Korean, the per-character segmentation differs. A single CJK character may be a valid token on its own because the character represents a full semantic unit. SOSL's minimum-length rule still applies, but the boundary is character-based rather than word-based.
For mixed-script inputs (a CJK character plus a Latin letter), the tokenizer may split unpredictably. Test the search behavior with realistic multilingual inputs.
Edge case: phrase searches and exact matches
Wrapping a multi-word term in quotes turns the search into a phrase query. FIND {"customer success"} finds records containing the exact sequence "customer success", not records that mention "customer" and "success" separately. Phrase queries have their own indexing characteristics and can be slower than token queries.
Single-word phrase queries are valid even for single characters if the platform treats them as exact-match identifiers. FIND {"Q"} may pass while FIND {Q} fails because the quotes change the parsing.
Edge case: case-insensitivity and accents
SOSL is case-insensitive by default. FIND {Smith} and FIND {smith} return the same results. Accented characters are folded to their base form for indexing, so Cafe and Café match each other.
The folding can be surprising. Searching for O'Reilly may match OReilly (with the apostrophe stripped) because the tokenizer removes punctuation. Test with realistic data that includes diacritics, apostrophes, and special characters.
Test patterns
Unit tests for the controller should cover the validation branches and the happy path.
@IsTest
static void searchReturnsEmptyForShortTerm() {
QuickFindController.SearchResults r = QuickFindController.search('a');
System.assertEquals(0, r.accounts.size());
System.assertEquals(0, r.contacts.size());
}
@IsTest
static void searchReturnsResultsForValidTerm() {
Account a = new Account(Name = 'Acme Corporation');
insert a;
Id fixedSearchId = Search.testGetFakeId(Account.SObjectType);
Test.setFixedSearchResults(new List<Id>{a.Id});
QuickFindController.SearchResults r = QuickFindController.search('Acme');
System.assert(r.accounts.size() > 0);
}
Test.setFixedSearchResults lets unit tests return deterministic SOSL outputs. The platform's search index is not populated in test contexts, so without the fixed-results helper, SOSL queries return empty lists even for matching data.
Diagnosing in production
When the error fires:
- Identify the SOSL query that threw. The exception name is unique to SOSL.
- Inspect the user input that was embedded in the query.
- Add input validation: minimum length, stop-word filter, debounce.
- Test the fix with single-character, empty, and whitespace inputs.
- Deploy.
For one-off production incidents, a hotfix that validates input at the controller level is usually fast. The deeper question is whether the UI should be making search calls at all for short inputs, and the right fix is usually a debounced input handler.
Anti-pattern: catching and ignoring the exception
A reflex response is wrapping the SOSL call in try/catch and swallowing the exception. The user sees an empty result list with no explanation. The bug appears fixed but the underlying validation problem remains, and the agent experience worsens because typing a single letter no longer hints "keep typing".
The right fix is to validate explicitly and provide a UI message. "Type at least two characters" is more helpful than silently empty results.
Defensive habits
Validate input before assembling SOSL queries. Length, character class, and content rules should run at the controller boundary.
Use bound parameters in SOSL. Concatenating user input into query strings invites injection.
Debounce search inputs in the UI. A 300-millisecond debounce reduces API calls by an order of magnitude for typical typing patterns.
Provide visible feedback for short inputs. A small hint below the search box ("type 2+ characters") guides users without confusing them.
Test with realistic multilingual data. The minimum-length rule and tokenization behavior differ for Asian scripts.
SOSL versus SOQL for full-text search
A frequent design question is whether to use SOSL or SOQL with LIKE clauses for a given search feature. The two have different strengths.
SOQL LIKE is exact-substring matching against a single field. WHERE Name LIKE '%Acme%' returns records whose Name contains "Acme". The query is precise but limited to one field at a time, and LIKE queries with leading wildcards bypass the field's index entirely.
SOSL searches across many objects and many fields in one call, with built-in relevance ranking, fuzzy matching, and stemming. For quick-find and global-search use cases, SOSL is the right tool. For exact-match lookups on a known field, SOQL LIKE is appropriate.
Performance: SOSL queries hit the search index, which is updated asynchronously from data writes. Newly created records may not appear in search for a few seconds. SOQL queries hit the live data and are immediately consistent.
For an admin who needs to find a specific record by partial Name, SOQL works fine. For a support agent who needs to find any record across Cases, Accounts, and Contacts that mentions a keyword, SOSL is the right choice.
Per-object filtering and field scoping
SOSL supports per-object filters and field scoping that narrow the result set without changing the FIND clause.
[
FIND :term
IN NAME FIELDS
RETURNING
Account(Id, Name WHERE Industry = 'Banking'),
Contact(Id, Name WHERE Email != null)
]
The IN NAME FIELDS clause restricts the search to name-type fields (Account.Name, Contact.Name, Case.Subject). Other clauses include IN ALL FIELDS (the default), IN EMAIL FIELDS, IN PHONE FIELDS, and IN SIDEBAR FIELDS.
The per-object WHERE clauses add additional filtering after the search index returns candidate records. Combining field scoping with per-object filters produces faster, more targeted queries.
Quick recovery checklist
- Find the controller that builds the SOSL query.
- Add a length check at the entry point.
- Use bound parameters.
- Add a UI hint for short inputs.
- Deploy and verify with single-character inputs.
The fix is short. The valuable improvement is the user-experience layer that prevents the error from ever firing in the first place.
Further reading from Salesforce
Related dictionary terms
Share this fix
Related SOQL errors
INVALID_FIELD: <field> is not filterable
SOQLYou used a field in `WHERE`, `ORDER BY`, or `GROUP BY` that the platform refuses to filter or sort on — almost always a Long Text Area, Rich…
INVALID_FIELD: No such column 'X' on entity 'Y'
SOQLEither the field truly doesn't exist on the object, or it exists but the running user / API session can't see it. The same error message is …
INVALID_FIELD: NULL_FOR_NON_REFERENCE_FIELD: <field> can not be null
SOQLYou assigned `null` to a field type that doesn't accept null — usually a Boolean (which expects `true` or `false`, not null) or a primitive …
MALFORMED_QUERY: unexpected token
SOQLThe 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. …
OPERATION_TOO_LARGE: Aggregate query has too many rows for direct assignment, use FOR loop
SOQLAn aggregate or relationship query returned more rows than Apex will assign to a `List<AggregateResult>` variable in one go. The platform te…