Redis vs. Elasticsearch: When You Need Search, and When You Need Both

Published on
September 2, 2026

The Question Behind the Question

This comparison is rarely a greenfield choice. The common starting position is that Redis is already there, holding sessions and cache entries, and a feature has appeared that needs search: a product catalogue with facets, an admin screen that has outgrown LIKE '%term%', a support tool that has to find tickets by phrase. The question is not usually which system is better. It is whether this new requirement justifies operating a second cluster.

One Stack Overflow thread puts it as "Both ElasticSearch and Redis, overkill usecase?". Three decisions hide inside it: whether the workload needs an inverted index at all, whether Redis can serve the queries you have, and, if you keep both, which system owns which data. The answers changed in 2025 and again in 2026.

If this is trueChoose
Tuned relevance is the product, and ranking quality drives revenueElasticsearch
Logs, metrics or audit trails; the corpus is far larger than RAMElasticsearch
Retention is per index, and analysis chains differ per languageElasticsearch
The corpus fits in memory; filtering is tag, numeric, prefix or geo, and relevance means BM25 with a few field weightsRedis alone
Results must be visible the instant the write returns, at full write throughputRedis alone
Records expire individually rather than by indexRedis alone
Both are deployed already and search is expensive but repetitiveBoth: cache result sets in Redis
You are on Valkey and need ranked resultsElasticsearch, or Redis; not valkey-search

Redis and Elasticsearch are not two NoSQL databases competing for the same slot. Elasticsearch is a search engine that happens to store documents; Redis is an in-memory data store that happens to index them. The overlap is real but partial, and the parts that do not overlap decide most of these arguments.

Where Elasticsearch Genuinely Wins

Analysis is composable. An Elasticsearch analyzer is a pipeline you assemble: a character filter, a tokenizer, then any number of token filters in an order you choose, and you can define one chain for indexing and a different one for querying the same field. Redis gives you a fixed set of switches instead: a stemmer language, a stopword list, synonym groups, phonetic matching, a suffix trie. Those switches cover a great deal. What they do not do is compose. When the requirement is "fold ASCII, then expand synonyms, then shingle, and do it differently in the search box than in the autocomplete", the ordering itself is the feature, and only one of these systems has it.

Relevance is a discipline with tools attached. Elasticsearch has function score queries, decay functions over recency or distance, rescore phases that re-rank the top window with a more expensive model, and learning-to-rank, native since 8.12 on a paid subscription tier. Redis has scoring, and a BM25STD default since 8.4, but that is not a ranking pipeline.

Aggregations over corpora larger than RAM. Elasticsearch stores segments on disk and pages them through the filesystem cache, so cluster capacity is bounded by disk rather than by memory, and aggregations run over corpora many times larger than the RAM available to them. Redis has aggregation too, with grouping, reducers and cursor-based reads over large result sets, but its index is memory-resident. Cross-cluster search extends the same query across independent clusters.

Lifecycle management is built in. Index Lifecycle Management rolls indices over on age or size, moves them between hot, warm and cold tiers, and deletes them on schedule; data streams express the same thing declaratively with data_retention. If your data has a retention policy rather than a per-record lifetime, this is machinery you would otherwise write and operate yourself.

A licensing note, since it is frequently misremembered in both directions. Elastic added AGPLv3 as an option in 2024, so Elasticsearch is now triple-licensed under AGPLv3, SSPL 1.0 and the Elastic License 2.0. Nothing was withdrawn in that change; an option was added, and the x-pack directory stayed ELv2-only, which is where learning-to-rank lives. That matters if licensing is the constraint rather than capability.

What Redis 8 Changed

The claim these comparisons usually settle on is that Redis "cannot match Elasticsearch for full-text search, complex queries or relevance scoring". Two of the three are now simply wrong: both are in the box. The third needs restating rather than reversing. Redis scores, but it has no ranking pipeline, which is a narrower complaint than the one usually made.

Redis 8.0, generally available in May 2025, merged Redis Stack into a single distribution. The Query Engine, JSON, time series and the probabilistic types are part of Redis Open Source: there is no separate Stack to install, and no new Redis Stack versions are being produced. Redis 8 also moved to a tri-licence, giving you a choice of RSALv2, SSPLv1 or AGPLv3. Any guide that tells you to install Redis Stack to get search is describing the world before May 2025.

On scoring specifically, Redis 8.4 made BM25STD the default scorer, replacing TFIDF. The full set is TFIDF, TFIDF.DOCNORM, BM25STD, BM25STD.NORM, BM25STD.TANH, DISMAX, DOCSCORE and HAMMING; the older BM25 name still resolves but is deprecated in favour of BM25STD. Stemming runs on Snowball across 31 languages, selectable per index, per document through a language field, or per query, with Chinese handled by the Friso tokenizer rather than Snowball. Redis 8.4 also added FT.HYBRID, which runs a lexical query and a vector query together and fuses the two result sets.

From Java, the Redis Search service takes the schema, the scorer, the highlighter and the stemming controls as ordinary builder calls. The schema builders live in org.redisson.api.search.index and the query options in org.redisson.api.search.query:

RSearch search = redisson.getSearch(StringCodec.INSTANCE);

// Documents are ordinary Redisson objects. This index is defined over JSON.
RJsonBucket<Product> bucket = redisson.getJsonBucket("product:1",
        new JacksonCodec<>(Product.class));
bucket.set(new Product("Trail running shoe", "Grippy outsole for wet rock",
        "footwear", 129.00));

// The title carries five times the weight of the body. Both are stemmed with
// the English Snowball stemmer, and the body also matches phonetically.
search.createIndex("product_idx", IndexOptions.defaults()
        .on(IndexType.JSON)
        .prefix("product:")
        .language("english")
        .stopwords(Arrays.asList("the", "and", "for")),
    FieldIndex.text("$.title").as("title").weight(5.0),
    FieldIndex.text("$.body").as("body").phonetic(PhoneticMatcher.DM_EN),
    FieldIndex.tag("$.category").as("category"),
    FieldIndex.numeric("$.price").as("price").sortMode(SortMode.NORMALIZED));

// Results come back in BM25STD relevance order, with highlighted snippets.
SearchResult result = search.search("product_idx",
    "@category:{footwear} trail running",
    QueryOptions.defaults()
        .scorer("BM25STD")
        .highlight(HighlightOptions.defaults()
            .fields(Arrays.asList("title", "body"))
            .tags("<mark>", "</mark>"))
        .summarize(SummarizeOptions.defaults()
            .fields(Arrays.asList("body"))
            .fragsNum(2)
            .fragSize(40))
        .returnAttributes(new ReturnAttribute("title"),
                          new ReturnAttribute("body"))
        .limit(0, 20));

System.out.println(result.getTotal() + " matching documents");
for (Document doc : result.getDocuments()) {
    System.out.println(doc.getId() + "  " + doc.getAttributes().get("title"));
}

Against the "no relevance scoring" claim: per-field boosting is weight(5.0), scorer selection is scorer("BM25STD"), and the result list arrives in score order. Highlighting with your own tags and summarised fragments are query options rather than post-processing, and profileSearch(...) returns a typed report of where query time went. Alongside these, FieldIndex.text(...) carries noStem() and withSuffixTrie() for suffix and infix matching, updateSynonyms(...) maintains synonym groups, addDict(...) backs a spellcheck(...) call that returns suggestions with scores over a configurable maximum edit distance, and aggregate(...) handles grouping and reducers with readCursor(...) for result sets too large to return at once.

Hybrid retrieval is a single call. hybridSearch takes a lexical query and a vector similarity clause and combines them with either reciprocal rank fusion or a weighted linear combination. Vector fields come from the same FieldIndex builder as everything else; our guide to vector similarity search on Java covers the indexing algorithms, the memory arithmetic and the query encoding, and there is background on Redis as a vector database if that is the immediate requirement.

The limits are real. The index is memory-resident, and it is also derived: Redis rebuilds it from the keyspace on restart and after a full resync, where Elasticsearch persists Lucene segments to disk. For a large index that rebuild belongs in your restart budget. And the "16x more query throughput" figure Redis publishes deserves two caveats: it is Redis's own number, measured against the previous single-threaded engine, and the search-workers setting that enables multi-threaded querying defaults to 0. An out-of-the-box Redis 8 does not get it.

Valkey Is Not Redis Here

For teams who moved to Valkey during the licence changes, this is the part that decides the architecture.

Search on Valkey is not in the core server. It is valkey-search, a separate BSD-3-licensed module, and its capabilities have tracked behind Redis. Vector, numeric and tag search came first. Full-text search arrived only in valkey-search 1.2.0, released 17 March 2026, and it requires Valkey 9.0.1 or later.

CapabilityRedis 8.4+valkey-search 1.2
Relevance rankingBM25STD by default, 8 scorers, scores returnableNone. No SCORER and no WITHSCORES
Stemming languages31, via SnowballSnowball present, but LANGUAGE accepts only ENGLISH
Per-field boostingWEIGHT, any valueWEIGHT accepts only 1.0, kept for interoperability
PackagingIn core since Redis 8.0Separate module; the published bundle image has lagged the module

The first row is the one that matters. A search engine that cannot rank is a filter: it will tell you which documents contain your terms, not which one to put first. For faceted filtering over a catalogue, or for vector retrieval where the distance supplies the ordering, that is often enough, and valkey-search handles it. Where result ordering is the product, it is not yet a substitute, and the second and third rows compound the problem, because per-field boosting and multilingual stemming are the two levers you would normally reach for first.

Modules reach most users through the valkey-bundle container image, and that image has lagged the module's own releases: as of the 9.0.1 bundle published in February 2026, it still shipped valkey-search 1.0.2, without text search. Check the module version inside the image you actually deploy rather than the version in the release notes you read. None of this applies to the wider Valkey and Redis comparison, where compatibility is close to total. Search is the sharpest divergence between the two projects today.

Four Things That Decide It in Practice

1. Indexing latency is not the same as write latency

Elasticsearch is near-real-time, and "near" has a number: index.refresh_interval defaults to 1 second. A document written now becomes searchable at the next refresh, not on acknowledgement. The index API can override this per request, with ?refresh=wait_for blocking until the next scheduled refresh and ?refresh=true forcing one, at a throughput cost that climbs steeply with write rate. There is also a second behaviour for indices that have not set the interval explicitly: a shard that has served no search traffic for index.search.idle.after (default 30 seconds) stops refreshing in the background. Results do not go stale as a result, because the next search triggers the deferred refresh and waits for it, but that first search after a quiet period pays for the work that was skipped.

Redis indexes synchronously as part of the write, so a document is queryable when the write returns. The one qualification is index creation rather than steady state: FT.CREATE backfills existing keys with a background scan, which is why IndexOptions offers skipInitialScan(true).

The difference, then, is not that Elasticsearch cannot make a write immediately visible. It is that Redis does so by default while Elasticsearch does so by opting out of the batching that makes it fast. For a catalogue that reindexes nightly, none of this matters. For "user edits their profile, then immediately searches for it", it is the entire bug report.

2. Elasticsearch has no per-document expiry

The _ttl field was deprecated in Elasticsearch 2.0 and removed in 5.0, and nothing replaced it at the document level. The supported patterns are time-based indices with ILM, data streams with a retention policy, or a scheduled delete-by-query, all of which operate on whole indices or on scans rather than on individual records.

Redis expiry is a per-key primitive. Where data is naturally per-record and short-lived, such as a search index over sessions, over pending orders, or over anything with an individual lifetime, the mismatch is structural rather than a matter of tuning: you either accept coarse-grained deletion or you operate a sweeper. Our note on Redis TTL covers the semantics and the eviction behaviour, and IndexOptions.temporary(...) extends the same idea to indices that should disappear when they go idle.

3. What each system costs to operate

Elasticsearch is a JVM cluster. Heap sizing, garbage collection, shard counts, disk watermarks and rolling upgrades are its ordinary operational surface. None of that is exotic; it is simply a second system with its own failure modes, its own runbook and its own on-call rotation.

The counterweight is memory, and it deserves a calculation rather than a rule of thumb. What Elasticsearch keeps on disk and pages through the filesystem cache, Redis holds in RAM: the documents themselves, plus the inverted index over every indexed text field. As a starting point, one million documents with three indexed text fields averaging 60 bytes is roughly 180 MB of raw text, which the inverted index typically at least doubles, before the keyspace you already store. Run that against the instance you would need and compare it with the cluster you were trying to avoid. Where the crossover falls depends entirely on your schema.

"Redis is a process you already run" is only an argument if you are willing to run search on that process. A search workload and your session or cache keyspace on the same instance compete for the same memory and the same CPU, and a heavy query will be felt by everything else on the node. Budget for a separate instance, or a separate Redis Cluster, and the "no second system" saving becomes a "smaller second system" saving instead. That is still usually worth having, but it is a different claim.

4. What Redis holds that Elasticsearch cannot

Elasticsearch is a search engine, and the comparison tends to forget everything else Redis is doing in the stack. Distributed locks, rate limiters, session storage, counters, leaderboards and job queues have no Elasticsearch equivalent and are not meant to. If search is the only thing pushing you toward a second cluster, weigh it against the patterns Redis is normally deployed for, which are mostly not search at all. The same reasoning runs through the neighbouring comparisons, including Redis and MongoDB, where the question is again what the second system is for rather than which is faster.

Both, and Who Owns What

Redis in front of Elasticsearch. Cache the result set, not the documents. Elasticsearch's own shard request cache will not do this for you: it caches only size=0 requests, never the hits themselves. Search results for a normalised query are a good cache entry: expensive to produce, cheap to store, and tolerant of being briefly stale. Cache the document bodies instead and you have built a second source of truth that will drift from the index.

// Canonical key: query, window, sort and sorted filters, so the same request
// always produces the same key whatever order the parameters arrived in.
Map<String, String> filters = request.filters();
String canonical = normalise(query)
        + "|" + offset + "|" + limit + "|" + sort + "|"
        + filters.entrySet().stream()
              .map(e -> e.getKey() + "=" + e.getValue())
              .sorted()
              .collect(Collectors.joining("&"));

String key = "search:" + sha256Hex(canonical);   // any stable digest will do

RMapCache<String, List<String>> cache = redisson.getMapCache("search-results");
List<String> hits = cache.get(key);
if (hits == null) {
    hits = searchClient.search(query, filters, offset, limit, sort);
    cache.fastPut(key, hits, 60, TimeUnit.SECONDS);   // TTL on this entry alone
}

searchClient, normalise and sha256Hex stand in for your own code; the Redisson calls are real API. The key includes the window and the sort, because a cache keyed on the query alone will serve page one to every request for page two. And RMapCache gives each entry its own lifetime using Redisson-managed metadata and an eviction task, not a native EXPIRE on the entry, so it is not quite the per-key primitive argued for earlier; on Redis 7.4 and later, RMapCacheNative uses server-side hash-field expiration instead and is the better choice where it is available, though its fastPut takes a Duration rather than a long and a TimeUnit.

The TTL is the design decision and it should be argued rather than copied. Sixty seconds says you will serve a minute-old result set in exchange for absorbing repeated queries, which is a good trade when a popular query drags a long tail of pagination requests behind it and a bad one when users expect their own edits to appear. The broader trade-offs are in our guide to Java caching strategies, and an RMap with a MapLoader will move the lookup behind the map interface if you would rather not hand-write it.

Redis owning the query outright. Reasonable when the corpus fits in memory, filtering is tag, numeric, prefix or geospatial, and relevance means BM25 with a couple of field weights rather than a tuned ranking pipeline. That covers most internal tooling, admin search, catalogue facets and typeahead, and it is the case where the second cluster genuinely is overkill. It is worth checking against what you run today, because production "search" is often a SCAN loop that should not be one, and a secondary index replaces it outright. If the queries are location-shaped, geospatial search in Java covers that path specifically.

Everything above needs a server with the Query Engine: Redis 8 has it in core, Valkey needs valkey-search with the limits in the table above, and a client library cannot add a capability the server does not have. And if you are using Spring AI or LangChain4j alongside this, both reach Redis through Jedis, so pairing either with Redisson means two client libraries and two connection pools against the same server. That is workable, but size the connection budget for both.

Frequently Asked Questions

Are Redis and Elasticsearch the same?

No. Elasticsearch is a search engine built on Lucene, storing an inverted index on disk and paging it through the filesystem cache. Redis is an in-memory data store whose Query Engine adds secondary indexing over data it already holds. The overlap is partial: both index JSON documents, and both do vector and geospatial search. Outside that, Redis also provides locks, counters, queues and session storage, while Elasticsearch provides analyzer pipelines, ranking tooling and index lifecycle management.

Is Redis just a cache?

It is most often deployed as one, but it has not been only that for a long time. Redis 8 ships JSON documents, time series, probabilistic structures and a query engine with full-text, numeric, tag, geospatial and vector indexing in the core distribution. Whether it should be your primary database is a separate question.

Is Elasticsearch still used?

Widely, and it remains the default answer for log analytics, observability and any search problem where ranking quality is the product. What has changed is the assumption that adding search to an application automatically means adding Elasticsearch. For corpora that fit in memory with filtering and BM25 relevance, that assumption is now worth testing rather than following.

Is Redis used in frontend or backend?

Backend. Redis is reached over a TCP protocol by server-side code and holds no browser-facing state; exposing it directly to a client would mean publishing credentials and an unauthenticated data store. In a Java application it sits behind your service layer, which is where a client such as Redisson runs.

Can Redis replace Elasticsearch?

For a corpus that fits in memory, with filtering on tags, numbers, prefixes or geography and relevance that BM25 plus a few field weights can express, yes, and the operational saving is real. For tuned ranking pipelines, composable multilingual analyzers, index lifecycle management, or corpora larger than RAM, no.

Is Redis faster than Elasticsearch?

On indexing latency the difference is structural rather than a matter of degree: Redis indexes synchronously, while Elasticsearch refreshes on an interval that defaults to one second and, on indices that have not set that interval explicitly, stops refreshing entirely after 30 seconds without search traffic. Elasticsearch can force a refresh per request, at a throughput cost. On query throughput, published benchmarks vary widely and most are run by one vendor or the other, so treat them accordingly. Redis's own 16x figure compares against its previous single-threaded engine and requires search-workers to be raised from its default of 0.

Does Redis support full-text search?

Yes, and since Redis 8.0 it is part of Redis Open Source rather than a separate Redis Stack install. Redis 8.4 made BM25STD the default scorer alongside TFIDF, DISMAX and five others, with Snowball stemming in 31 languages, stopword lists, synonym groups, per-field weights, phonetic matching, highlighting and summarisation. FT.HYBRID combines lexical and vector retrieval in one query.

Does Valkey support full-text search?

Only through the separate valkey-search module, and only since version 1.2.0 in March 2026, which requires Valkey 9.0.1 or later. It has significant limits compared with Redis: no relevance ranking, since there is no scorer selection and no way to return scores; LANGUAGE accepts only English; and WEIGHT accepts only 1.0. It filters well, but it does not rank.

Should I use Redis to cache Elasticsearch queries?

It is worthwhile when the same queries repeat and you can name the staleness you accept. Cache the result set keyed on the normalised query, its window, its sort and its sorted filters, with a short TTL, rather than caching document bodies, because caching the documents creates a second copy of your data that will drift from the index.

For implementation details, see how to search data stored in Redis on Java, how to store JSON with Redis on Java, and vector sets in Valkey and Redis. To see which capabilities are in the open-source edition and which are not, compare Redisson and Redisson PRO.