About the demo

Three ways to search for a movie

Elastiflix-Movies Java searches a movie catalog stored in an Elasticsearch Serverless index. With the same query you can try three search modes and see how the results change with each one: by words, by meaning, or by combining both. You can also narrow the results with filters and load new movies into the catalog.

Full-text (lexical) Finds movies whose title contains the words in your query. BM25 · title, original_title Vector Finds movies whose plot is close in meaning to what you describe. k-NN · plot_256, plot_512, plot_1024 Hybrid Runs both searches above and merges their results into a single list. BM25 + k-NN · RRF fusion

Full-text (lexical)

Matches terms in the title and original_title fields, both of type text. Elasticsearch analyzes the query text into tokens and compares them with the indexed tokens of each title.

How relevance is scored: BM25

BM25 is Elasticsearch's default scoring algorithm for text fields. It favors query terms that appear in the field and are rare across the catalog. Two parameters control its behavior: k1 (default 1.2) limits how much a repeated term adds, and b (default 0.75) adjusts the score for field length.

Available settings

Query type: match
Searches a single field, which you choose: title or original_title.
Query type: multi-match
Searches both fields at once with the best_fields type: each field is scored separately and the movie gets the score of the best-matching field.
Operator: OR
Any one of the query words is enough. This is Elasticsearch's default behavior.
Operator: AND
All the words must appear. With multi-match and best_fields, they must all be in the same field: they can't be split between title and original_title.

Documentation Match query Multi-match query Similarity (BM25)

Vector

Searches by meaning in each movie's plot (the plot field). Your query text is turned into a vector with the jina-embeddings-v5-omni-small model, and Elasticsearch returns the movies whose plot vectors are closest, using the knn retriever.

How k-NN works

k-NN (k-nearest neighbors) finds the vectors closest to the query vector. Elasticsearch does this approximately over an HNSW graph: it walks the graph instead of comparing against the whole catalog, which makes it fast but not always exact.

The three plot representations

This mode is meant to test how the number of vector dimensions affects result precision. All three fields come from the same model: its native dimension is 1024, and it supports Matryoshka truncation to 256 and 512.

FieldTypeDimensionsSource
plot_256dense_vector256First values of the 1024-dimension vector, renormalized (Matryoshka)
plot_512dense_vector512First values of the 1024-dimension vector, renormalized (Matryoshka)
plot_1024dense_vector1024Full model vector

A fair comparison. All three fields use the same index settings: index_options of type hnsw with unquantized float vectors, the same m (16) and ef_construction (100), which are Elasticsearch's defaults, and cosine similarity. That way, the only difference between them is the number of dimensions.

Measured precision by dimension

Measured on the original catalog of 4,322 movies. Measured with 32 plot descriptions (26 in Spanish and 6 in English), each with a single correct movie in the catalog and no words from its title. The ranks were obtained in Elasticsearch and confirmed with an exact brute-force search: they match in every case.

FieldCorrect at rank 1In the top 5In the top 10MRR@10
plot_25622 of 32 (69%)29300.774
plot_51225 of 32 (78%)31320.853
plot_102426 of 32 (81%)31310.880

MRR rewards ranking the correct movie near the top: it is 1 if the movie always comes 1st and 0.5 if it always comes 2nd. In 24 of the 32 queries the rank is the same in all three fields; the differences are concentrated in the hardest descriptions:

Movieplot_256plot_512plot_1024
Inception21st4th3rd
Life of Pi10th3rd2nd
Spirited Away4th1st1st
Titanic4th3rd1st
E.T. the Extra-Terrestrial3rd1st1st
Coco2nd1st1st
  • 256 dimensions clearly lose precision; 512 comes very close to 1024 at half the size.
  • More dimensions aren't always better in every case: Forrest Gump ranks 21st, 8th and 18th.
  • With 32 queries this is a clear trend, not a firm statistical conclusion.

Available settings

Vector k-NN
The field to search: plot_256, plot_512 or plot_1024. More dimensions usually capture more nuances of meaning, at the cost of more memory and more expensive comparisons.
k
Maximum number of results the vector search returns, after merging the candidates from all shards; they are split into pages of 25, 50 or 100. Must be less than or equal to num_candidates. Initial value: 100.
num_candidates
Candidates evaluated on each shard before keeping the best k. Raising it tends to improve precision, at the cost of longer response times. Maximum 10,000. Initial value: 200.

Implementation note: for retrieval, the model expects the Query: prefix on queries. Plots are vectorized with input_type ingest and no prefix.

Documentation kNN retriever Approximate kNN Dense vector jina-embeddings-v5-omni-small

Hybrid

Combines the two modes above with the same settings. The rrf retriever wraps a standard retriever, with the match or multi_match query from the full-text component, and a knn retriever from the vector component.

  1. 1 · Full-text component List sorted by BM25 over title and/or original_title.
  2. 2 · Vector component List sorted by k-NN proximity on the chosen plot vector.
  3. 3 · RRF fusion A single final list, sorted by each movie's position in both lists.

How Reciprocal Rank Fusion works

RRF combines the two lists by each movie's position in them, not by its score, because BM25 scores and vector similarity use scales that can't be compared. A movie that ranks high in both lists moves up in the final result.

Fusion parameters

The demo doesn't expose them in the UI: rank_constant uses Elasticsearch's default and rank_window_size is set by the application.

rank_constant = 60
The higher it is, the more weight movies further down each list get.
rank_window_size = k
How many results from each component go into the fusion, and how many combined results you can page through. The application sets it equal to k (100 by default), so both components contribute the same number and pages of 25, 50 or 100 are never empty.

Documentation RRF retriever

Results and filters

Results are shown in pages, with the score that matches each mode, and can be narrowed with filters computed from the results themselves.

Pagination

25 · 50 · 100 per page
You choose the page size and move with Previous, Next or a page number. Changing the size or applying filters takes you back to page 1.
Full-text
You can page through all matches with from and size, up to the 10,000 limit set by index.max_result_window.
Vector
There are at most k results (100 by default), split into pages.
Hybrid
There are at most rank_window_size combined results. The application sets it equal to k, so from + size never exceeds the window and no page is empty.

Score of each result

Full-text
BM25 score. It has no fixed scale: it's only useful for comparing results within the same search.
Vector
k-NN similarity. With cosine, Elasticsearch returns (1 + cosine) / 2, a value between 0 and 1.
Hybrid
The BM25 score and the k-NN similarity, each with the movie's position in its list, plus the RRF score that sets the final order.

Filters

Fields
adult, genres, release_date (from–to range), spoken_languages and rating.
Combination
Across different filters, all must match; within a single filter, any one of the checked values is enough. Changes take effect when you press APPLY; the “From” date can't be later than “To”.
Dynamic values
Each filter shows only the values present in the current results, with how many results have each one, and they're recalculated whenever the results change. Values you've already checked stay visible so you can uncheck them.
In Elasticsearch
Full-text: filters go in the query's filter context, which doesn't affect the BM25 score, and the values come from terms aggregations. Vector: filters go in the filter of the knn retriever, which applies them during the search, so it returns k movies that match them. Hybrid: they go in the filter of the rrf retriever, which applies to both searches.
Values in Vector and Hybrid
The application computes them from the returned results (up to k). With RRF, aggregations would include every match from each search, even movies that don't appear in the combined list.

When there are no results

If the search finds nothing, the page says so and suggests settings for the mode (for example, OR instead of AND). If the search did find movies but none match the filters, it explains this and offers to remove them.

Documentation Pagination Terms aggregation Filtered kNN search RRF retriever

Loading data

You can add movies one at a time or import a file. A file can be loaded in two modes: append, which adds the movies to the catalog, or bulk, which replaces it. In both, the whole file is validated before anything is written.

Ways to load data

Single record
A form with the mapping fields. id, title and plot are required.
.json file
The index export (an object with documents and, optionally, mapping and document_count), an array of movies, or NDJSON.
.csv file
UTF-8 text with or without BOM, comma-separated, with a header. A CSV without a header is rejected. Lists and nested objects go as JSON inside the cell.

Validation

Before uploading
Only .json or .csv, no double extensions, up to 25 MB, with UTF-8 text content. The server repeats these checks.
Against the mapping
The whole file is checked: every field must exist in the mapping and have its type (release_date as yyyy-MM-dd, adult as a boolean, keywords and belongs_to_collection as nested objects). id can't be repeated.
If something doesn't match
The load stops and no records are imported. The page shows each error with its record, id, field, received value and what the mapping expects, and lets you download the full error report.
Warnings
Anything that doesn't break the mapping doesn't stop the load: for example, empty values inside a list are skipped and empty strings are stored without a value.

Import

Vectors
The three vectors are always generated from plot with the Elasticsearch inference service (jina-embeddings-v5-omni-small, input_type ingest and no prefix). If the file includes plot_256, plot_512 or plot_1024, they're ignored.
Append mode (default)
The movies in the file are added to the current index through the alias, in batches of 100 and with no temporary index. If an id already exists, the movie is replaced. If a batch fails or the load is cancelled, whatever was already written stays and the page tells you how many movies were added.
Bulk mode
The movies in the file are written in batches of 100 to a new temporary index that holds only the movies in the file: the current catalog is not copied.
Publication (bulk)
When it finishes, the count is checked and the elastiflix-reloaded alias moves to the new index in a single atomic operation. The previous index is deleted.
If it fails (bulk)
If Elasticsearch rejects a batch or the check doesn't add up, the temporary index is deleted and the catalog stays exactly as it was.

Documentation Index aliases Dense vector

Credits and terms of use

Academic and demonstration purposes

Elastiflix-Movies Java is an academic project that demonstrates full-text, vector, and hybrid search techniques. It is not a commercial product: it is not sold, it shows no advertising, and in no way does it intend to make a commercial profit from itself or from the information it uses.

Credit for the movie information goes to TMDB and Wikipedia.

Movie data and poster images come from TMDB (The Movie Database).

This product uses the TMDB API but is not endorsed or certified by TMDB.

Este producto usa la API de TMDB, pero no está avalado ni certificado por TMDB.

Movie texts come from Wikipedia, whose content is published under the Creative Commons Attribution-ShareAlike 4.0 license.

Names, trademarks, posters, and texts belong to their respective owners. This demo is not affiliated with or endorsed by TMDB or Wikipedia.