Which field values does Elasticsearch's exists query treat as missing?
answer
- It asks about the index, not your JSON
- Absent and null land in the same bucket
- An empty array indexes nothing
- An empty string is still a value
- Mapping parameters can silently skip a value
basics
~20 sThe exists query matches documents that have an indexed value for a field. A JSON null, an empty array, and an array of only nulls all count as missing, as do values skipped by ignore_above or ignore_malformed.
solid answer
~40 s`{"exists": {"field": "user.id"}}` matches documents where the field has an indexed value. Missing means more than "the key is absent from the JSON": `null`, `[]` and `[null, null]` all produce no indexed value and therefore do not match, while an empty string `""` *is* a value and does match. Two mapping-driven cases catch people out: a value longer than the field's `ignore_above` is not indexed at all, and a malformed value skipped by `ignore_malformed` is likewise absent — in both cases the value is still visible in `_source`, so the document looks like it should match and does not. To find documents *without* a value, wrap it: `bool` → `must_not` → `exists`. There is no `missing` query; it was removed years ago in favour of that form.
code
json · 8 lines// documents that have no value for user.id
{
"query": {
"bool": {
"must_not": { "exists": { "field": "user.id" } }
}
}
}go deeper
Be ready to write the exists query and its negation with bool and must_not, and to say that null and an empty array both count as missing.
Explain that exists consults indexed structures rather than _source, and name the mapping parameters that can make a visibly present value invisible to it.
Show the diagnostic instinct: when the application and search disagree about a field being present, go to the mapping — ignore_above, ignore_malformed, index false, or the wrong multi-field.
Own the data-contract angle: which fields are mandatory, how absence is represented across producers, and whether null_value or a sentinel belongs in the mapping standard.
## What exists actually asks The `exists` query takes a single `field` parameter and matches documents for which Elasticsearch has an indexed value for that field. The important word is *indexed*. It is not asking whether the key appears in the JSON you sent, and it is not reading `_source`. That distinction is the source of every surprise this query produces. ## The values that count as missing A document does **not** match `exists` when: - **The field is absent** from the indexed document entirely. - **The value is `null`.** JSON null is explicitly "no value" and nothing is indexed for it. - **The value is an empty array `[]`**, or an array containing only nulls such as `[null, null]`. An array is indexed as its elements; with no non-null elements there is nothing to index. - **The value exceeded `ignore_above`** on a `keyword` field. That mapping parameter tells Elasticsearch to skip indexing values longer than N characters. Nothing is indexed, so `exists` does not see it — even though the full string is returned in `_source`. This is the most confusing case in practice, because the dynamically created `.keyword` sub-field of a string carries `ignore_above: 256`, so long values quietly fall out of exact-match queries *and* `exists`. - **The value was malformed and `ignore_malformed` is set.** Sending `"abc"` to a field mapped as `integer` normally fails the whole document; with `ignore_malformed: true` the document is indexed with that one field skipped. Again `_source` keeps the bad value while the index has nothing. And the case that surprises people the other way: **an empty string is a value**. `{"comment": ""}` on a `keyword` field indexes a zero-length term, and `exists` matches. If your definition of "has a comment" excludes the empty string, `exists` alone will not express it — you need an additional clause. ## Finding the documents without a value There is no `missing` query in modern Elasticsearch; it was removed and the idiom is negation: ```json { "query": { "bool": { "must_not": { "exists": { "field": "user.id" } } } } } ``` This is a filter-shaped clause — it does not contribute to relevance — so it belongs in `filter` or `must_not` rather than `must`, and it is a good candidate for caching when reused. ## The null_value escape hatch If you want nulls to be searchable, map the field with `null_value`. On a `keyword` field, `"null_value": "NULL"` causes an explicit JSON `null` to be indexed as the term `NULL`. Two consequences follow: those documents now match `exists`, and you can search for them explicitly with a term query for `NULL`. The parameter's type must match the field's — a string for `keyword`, a number for a numeric field — and it substitutes only for explicit nulls, not for an absent field. It is the right tool when "the caller explicitly said none" is semantically different from "the caller said nothing", such as an optional attribute the user actively cleared. ## Multi-fields and object paths `exists` on `title` and `exists` on `title.keyword` can disagree, because they are different indexed fields with different mapping parameters — the `ignore_above` case above is exactly that. Name the sub-field you actually query. On object fields, `exists` with a parent path such as `user` matches documents having an indexed value for any leaf under it, since the object itself is not a field but a namespace of `user.id`, `user.name` and so on. ## Where it shows up in real work The most common production use is data-quality auditing: counting how many documents in an index lack a field you thought was mandatory, usually right after a mapping or pipeline change. The second is optional-filter logic — "only items that have a discount" — where a naive implementation checks `_source` in the application and disagrees with what search returns. When those two disagree, the answer is almost always `ignore_above`, `ignore_malformed`, or a field mapped with `index: false`, and the fix is to reconcile the mapping with what the application believes, not to work around it at query time.
- How do you find documents that are missing a field, given there is no missing query?Negate the exists query: a `bool` with `must_not` containing `{"exists": {"field": "..."}}`. The `missing` query was removed from the DSL and this is the supported idiom. Keep it in filter or must_not context so it contributes no score and stays cacheable.
- A value is clearly present in _source but exists returns false. What do you check first?The mapping. The usual causes are `ignore_above` on a keyword field skipping a long value, `ignore_malformed` skipping a value of the wrong type, or querying the wrong member of a multi-field. `_source` is stored verbatim and is not what exists consults, so a visible value proves nothing about what was indexed.
- What does the null_value mapping parameter change about exists?It substitutes an explicit JSON null with a real indexed term of your choosing, so those documents start matching `exists` and can be found by a term query for that placeholder. It applies only to explicit nulls, not to an absent field, and its type must match the field's type.
saying these in an interview costs you the question
- Says exists reads the _source document
- Claims an empty string counts as missing
- Expects a missing query to still exist in the DSL
- Overlooks ignore_above hiding long values from exists
- Assumes null and a null_value placeholder behave the same