Using Couchbase Full‑Text Search: Create, Query, and Avoid Common Pitfalls
Learn how to set up a Couchbase Full‑Text Search (FTS) index, run queries from Java, and what to watch out for—analyzers, index health, and numeric fields.
01 Sept 2025, 15:59 UTC

Problem and Key Takeaway
You need quick, flexible search across unstructured JSON stored in Couchbase. N1QL can filter on fields, but it doesn’t provide token‑based matching, stemming, or relevance ranking. Couchbase’s Full‑Text Search (FTS) solves this by building a Lucene‑powered index that can be queried from Java with SearchQuery and SearchOptions. The key is that FTS indexes are separate from N1QL indexes, require their own definition, and are asynchronous. If you skip the index‑creation step or mis‑configure analyzers, you’ll get no results or performance hits.
Setting Up an FTS Index
FTS indexes live in the fts namespace of a bucket. The simplest way to create one is via the Web Console, but the REST API is often preferred for automation. Below is a minimal JSON definition that creates an index called products-fts on bucket products:
{
"name": "products-fts",
"sourceType": "couchbase",
"sourceName": "products",
"type": "fulltext-index",
"planParams": {
"maxPartitionsPerPIndex": 8
},
"params": {
"doc_config": {
"docid_prefix_delim": "::",
"docid_regexp": "^products::.*$"
},
"mapping": {
"default_mapping": {
"enabled": true,
"analyzer": "standard",
"indexAnalyzer": "standard",
"store": false
},
"field_mapping": {
"price": {
"type": "numeric",
"index": true
}
}
}
}
}
Run this payload against /api/indexes with an admin user and wait for the index status to become Ready. The docid_prefix_delim and docid_regexp fields restrict the index to documents whose IDs start with products::, which keeps the index lean.
Index Status Lifecycle
| Status | Meaning |
|---|---|
| Pending | Index definition is accepted but not yet built. |
| Building | Indexing data; queries will fail until complete. |
| Ready | Index can serve queries. |
| Failed | Index build encountered an error. |
Querying from Java
Assuming you have a Cluster instance connected to the cluster, the following snippet shows a typical search for the term “wireless” in the description field:
import com.couchbase.client.java.Cluster;
import com.couchbase.client.java.query.QueryResult;
import com.couchbase.client.java.search.SearchQuery;
import com.couchbase.client.java.search.SearchOptions;
import com.couchbase.client.java.search.result.SearchResult;
Cluster cluster = Cluster.connect("localhost", "username", "password");
SearchResult result = cluster.searchQuery(
"products-fts",
SearchQuery.term("wireless")
.field("description")
.limit(10),
SearchOptions.searchOptions()
.build());
for (SearchResult.SearchRow row : result.rows()) {
System.out.println("Doc ID: " + row.id());
// If you used the 'store' option, you can get the full document:
// System.out.println(row.content());
}
cluster.disconnect();
Key points:
SearchQuery.termcreates a simple term query; useSearchQuery.fullTextfor a full‑text string.- The
fieldmethod scopes the query to a specific field. - By default the query returns only document IDs; set
storetotruein the index definition or useSearchOptions.store(true)to embed the whole document. - Pagination is controlled with
limitandskipon the query.
Handling Numeric Fields
FTS does not index numeric fields unless you explicitly declare them in field_mapping with type: "numeric". The example above shows how to add a price field. You can then query numeric ranges with SearchQuery.range("price", 10, 50).
Common Pitfalls and Limits
1. Index Not Ready: Queries will throw com.couchbase.client.core.error.SearchException: no index found if the index is still building. Always poll /api/indexes/{name} until status is Ready.
2. Analyzer Mismatch: Using a keyword analyzer on a field that contains tokenized text will prevent matches. Stick to standard or simple for most English text, and consider snowball for stemming.
3. Large Result Sets: Returning thousands of rows without pagination can overwhelm the SDK and the network. Always use limit and skip or SearchOptions.scroll for deep paging.
4. Disk and CPU Usage: Each index consumes disk space proportional to the size of the indexed fields and CPU for building and querying. Monitor index memory usage in the FTS console and adjust maxPartitionsPerPIndex if necessary.
5. Missing Fields: By default the default_mapping will index all fields, but if you enable index: false on a field you must manually add it to field_mapping if you later want it searchable.
6. Multilingual Data: If you store content in multiple languages in the same field, a single analyzer may not surface all matches. Use language‑specific analyzers or separate fields per language.
Practical Verification Steps
- Create the bucket
productsand enable FTS. - Deploy the index definition above via the REST API.
- Insert sample documents with IDs prefixed by
products::and apricefield. - Run the Java snippet and confirm the returned IDs match the expected documents.
- Toggle the
storeoption and verify the full JSON appears in the result rows. - Deliberately change the analyzer to
keywordon thedescriptionfield and observe that the same query returns zero hits.
These steps validate both the index build and the query logic, ensuring that your application will behave as expected in production.
Conclusion
Full‑Text Search in Couchbase gives you powerful, Lucene‑based search capabilities that complement N1QL. By correctly defining the index, respecting analyzer semantics, and handling numeric fields explicitly, you can build responsive, relevant search experiences. Remember that index health is asynchronous, and that large result sets should be paginated. With these guidelines, you’ll avoid the most common mistakes and leverage FTS to its fullest potential.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.