Neo4j Text Search: Native Full-Text Index vs. Elasticsearch Integration
A decision guide for choosing between Neo4j's native Lucene-based indexes and Elasticsearch integration based on latency, consistency, and scale.
25 Mar 2026, 05:45 UTC

When implementing text search in Neo4j, the primary challenge is balancing the need for advanced search capabilities—such as fuzzy matching and language-specific stemming—against the operational cost of maintaining additional infrastructure. The decision typically hinges on whether your application requires strict ACID consistency or can tolerate the eventual consistency of an external search engine.
Comparing Search Architectures
| Feature | Native Full-Text Index | Elasticsearch via Neo4j Streams |
|---|---|---|
| Deployment | Embedded (Lucene-based) | External Cluster + Streams Plugin |
| Consistency | Strong (ACID) | Eventual (Asynchronous) |
| Query Power | Basic Lucene syntax | Full Elasticsearch DSL & Aggregations |
| Ops Overhead | Low (Managed by Neo4j) | High (Cluster management, mapping) |
| Licensing | Community & Enterprise | Enterprise (for full monitoring) |
Engineering Trade-offs
Native Full-Text Indexes are the optimal choice for teams prioritizing simplicity. Because they are managed by Neo4j, updates to the index happen within the same transaction as the data update. However, they are limited to the standard Lucene analyzer; if you need custom tokenization or complex linguistic analysis, procedural work is required.
Elasticsearch integration via Neo4j Streams introduces eventual consistency; writes may lag behind Neo4j transactions depending on Streams configuration. This approach offers richer query DSL and horizontal scaling at the cost of an extra cluster and complexity.
Implementing Native Indexes
To create a native full-text index, run the following command in the Neo4j browser (requires administrative privileges):
CALL db.index.fulltext.createNodeIndex('myIndex', ['Label'], ['prop1','prop2']);To query the index, use the tilde operator for fuzzy/wildcard matching:
CALL db.index.fulltext.queryNodes('myIndex', 'search~') YIELD node RETURN node;To verify that the index is actually being utilized, use the EXPLAIN or PROFILE prefix before your query. If the plan shows a NodeFulltextScan, the index is working.
Configuring Elasticsearch via Neo4j Streams
If you require advanced analytics or horizontal scaling, you must configure an Elasticsearch sink. This requires modifying the streams.conf file in the Neo4j configuration directory:
# Example sink configuration sinks.elasticsearch.url=http://elasticsearch-host:9200 sinks.elasticsearch.index=my_index sinks.elasticsearch.batch.size=1000
Once configured, insert data into Neo4j and confirm documents appear in the Elasticsearch index. To check for replication lag, compare Neo4j transaction timestamps with the @timestamp field in Elasticsearch. Acceptable lag should be under your defined Service Level Agreement (SLA).
Limitations and Cautions
- Native full-text indexes are limited to the features exposed by the Lucene standard analyzer; custom analyzers require complex setup.
- Using Elasticsearch with Neo4j Streams may require an Enterprise license for certain monitoring features; verify license compliance.
- Asynchronous synchronization means a node might not be immediately searchable in Elasticsearch after the Neo4j transaction commits.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.