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)
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:
titleororiginal_title. - Query type: multi-match
- Searches both fields at once with the
best_fieldstype: 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 betweentitleandoriginal_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.
| Field | Type | Dimensions | Source |
|---|---|---|---|
| plot_256 | dense_vector | 256 | First values of the 1024-dimension vector, renormalized (Matryoshka) |
| plot_512 | dense_vector | 512 | First values of the 1024-dimension vector, renormalized (Matryoshka) |
| plot_1024 | dense_vector | 1024 | Full 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.
| Field | Correct at rank 1 | In the top 5 | In the top 10 | MRR@10 |
|---|---|---|---|---|
| plot_256 | 22 of 32 (69%) | 29 | 30 | 0.774 |
| plot_512 | 25 of 32 (78%) | 31 | 32 | 0.853 |
| plot_1024 | 26 of 32 (81%) | 31 | 31 | 0.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:
| Movie | plot_256 | plot_512 | plot_1024 |
|---|---|---|---|
| Inception | 21st | 4th | 3rd |
| Life of Pi | 10th | 3rd | 2nd |
| Spirited Away | 4th | 1st | 1st |
| Titanic | 4th | 3rd | 1st |
| E.T. the Extra-Terrestrial | 3rd | 1st | 1st |
| Coco | 2nd | 1st | 1st |
- 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_512orplot_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 · Full-text component List sorted by BM25 over
titleand/ororiginal_title. - 2 · Vector component List sorted by k-NN proximity on the chosen plot vector.
- 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
fromandsize, up to the 10,000 limit set byindex.max_result_window. - Vector
- There are at most
kresults (100 by default), split into pages. - Hybrid
- There are at most
rank_window_sizecombined results. The application sets it equal tok, sofrom + sizenever 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_languagesandrating.- 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
filtercontext, which doesn't affect the BM25 score, and the values come fromtermsaggregations. Vector: filters go in thefilterof theknnretriever, which applies them during the search, so it returnskmovies that match them. Hybrid: they go in thefilterof therrfretriever, 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,titleandplotare required. - .json file
- The index export (an object with
documentsand, optionally,mappinganddocument_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_dateas yyyy-MM-dd,adultas a boolean,keywordsandbelongs_to_collectionas nested objects).idcan'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
plotwith the Elasticsearch inference service (jina-embeddings-v5-omni-small,input_typeingestand no prefix). If the file includesplot_256,plot_512orplot_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
idalready 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-reloadedalias 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
The catalog
The catalog lives in elastiflix-reloaded, an alias that points to the current index: searches always read the published catalog. A bulk load is published all at once or discarded without affecting searches; an append load is added to the current index batch by batch. It has 6,899 movies. These are all its properties, the ones in elastiflix-reloaded-mapping.json, grouped as in the «Load data» form. The «Used in» column says where the demo uses them: the card is each search result and the detail, each movie page.
The last two columns say what a file must include to be loaded, in append or bulk mode (the rules are the same): in a .json, every movie has id, title and plot with a value; in a .csv, the header has those three columns and every row, their value. The other fields are optional, but if present they must match their type, and a field or column that is not in these tables rejects the file.
Identification and titles
| Field | Type | Content | Used in | Required in .json | Required in .csv |
|---|---|---|---|---|---|
| id | keyword | Movie identifier in TMDB; it is also the document _id | Detail | Yes | Yes |
| imdb_id | keyword | IMDb identifier, for example tt0120338; the detail page links to it | Detail | No | No |
| title | text | Movie title | Full-text, Hybrid, card and detail | Yes | Yes |
| original_title | text | Original title | Full-text, Hybrid, card and detail | No | No |
| original_language | keyword | Original language, as an ISO 639-1 code: en, es, fr… | Detail | No | No |
Plot and text
| Field | Type | Content | Used in | Required in .json | Required in .csv |
|---|---|---|---|---|---|
| plot | text | Plot; source of the three vectors | Card and detail; source of the vectors | Yes | Yes |
| overview | text | TMDB overview | Detail | No | No |
| tagline | keyword | The movie's tagline | Detail | No | No |
| plot_256 | dense_vector | 256-dimension vector: the first 256 values of the 1024 one, renormalized | Vector, Hybrid | No; if present, it is ignored and generated | No; if present, it is ignored and generated |
| plot_512 | dense_vector | 512-dimension vector: the first 512 values of the 1024 one, renormalized | Vector, Hybrid | No; if present, it is ignored and generated | No; if present, it is ignored and generated |
| plot_1024 | dense_vector | 1024-dimension plot vector, generated with jina-embeddings-v5-omni-small | Vector, Hybrid | No; if present, it is ignored and generated | No; if present, it is ignored and generated |
Rating, date and status
| Field | Type | Content | Used in | Required in .json | Required in .csv |
|---|---|---|---|---|---|
| genres | keyword | Genres, for example Drama, Comedy or Action | Filter, card and detail | No | No |
| keywords | nested | TMDB keywords: a list of objects with id and name | Detail | No | No |
| keywords. | long | Keyword identifier in TMDB | Detail | No | No; it goes in the JSON of its object's cell |
| keywords. | text | The keyword, for example iceberg | Detail | No | No; it goes in the JSON of its object's cell |
| rating | keyword | Rating: G, PG, PG-13, R, NC-17 or NR | Filter and detail | No | No |
| release_date | date | Release date as yyyy-MM-dd (measured on the original catalog of 4,322 movies: 1902 to 2023) | Filter, card and detail | No | No |
| runtime | integer | Runtime in minutes | Card and detail | No | No |
| status | keyword | Production status, for example Released | Detail | No | No |
| adult | boolean | Adult content (measured on the original catalog of 4,322 movies: no movie in the catalog is flagged) | Filter and detail | No | No |
| video | boolean | Whether the record is a video rather than a theatrical movie | Detail | No | No |
Metrics
| Field | Type | Content | Used in | Required in .json | Required in .csv |
|---|---|---|---|---|---|
| popularity | float | Popularity according to TMDB | Detail | No | No |
| vote_average | float | Average vote, from 0 to 10 | Card and detail | No | No |
| vote_count | integer | Number of votes | Card and detail | No | No |
| user_score | float | User score | Detail | No | No |
| budget | float | Budget in USD; empty or 0 is shown as N/A | Detail | No | No |
| revenue | float | Revenue in USD; empty or 0 is shown as N/A | Detail | No | No |
Cast, production and media
| Field | Type | Content | Used in | Required in .json | Required in .csv |
|---|---|---|---|---|---|
| cast | keyword | Cast: a list of names | Detail | No | No |
| production_companies | keyword | Production companies | Detail | No | No |
| production_countries | keyword | Production countries | Detail | No | No |
| spoken_languages | keyword | Spoken languages, by their native name: English, Español, Français… | Filter and detail | No | No |
| poster_path | text | Poster path on the TMDB image CDN | Card and detail | No | No |
| backdrop_path | keyword | Backdrop path on the TMDB image CDN | Detail | No | No |
| homepage | keyword | Official website; it is only linked if it is an http or https address | Detail | No | No |
| belongs_to_collection | nested | Collection or saga it belongs to: an object with id, name, poster_path and backdrop_path | Detail | No | No |
| belongs_to_collection. | long | Collection identifier in TMDB | Detail | No | No; it goes in the JSON of its object's cell |
| belongs_to_collection. | text | Collection name, for example Harry Potter Collection | Detail | No | No; it goes in the JSON of its object's cell |
| belongs_to_collection. | text | Collection poster path | Detail | No | No; it goes in the JSON of its object's cell |
| belongs_to_collection. | text | Collection backdrop path | Detail | No | No; it goes in the JSON of its object's cell |
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.