Skip to content
ExponaBLOG
ELASTICSEARCH & SEARCH ENGINEERING · DAY 028 / 100

Nested vs object fields: a practical Elasticsearch guide

Model arrays of objects correctly.

Expona Admin7 min read

Introduction

When you're building search features, the way you model your data in Elasticsearch makes the difference between queries that work and ones that silently return wrong results. The choice between nested vs object fields is one of those decisions that can haunt you later if you get it wrong: one flattens your array data and loses relationships, while the other preserves them but costs more in storage and query performance. This guide walks you through when and how to use each, with concrete examples from real production systems.

Object fields — Arrays are flattened — field relationships are lost

By default, Elasticsearch treats arrays of objects as object fields. When you index an object field containing an array, Elasticsearch does something that surprises most developers: it flattens the array structure entirely.

Let's say you're building a marketplace and you're indexing a product with multiple reviews. Your document might look like this:

json
{
  "product_id": 1,
  "name": "Wireless Headphones",
  "reviews": [
    {
      "author": "alice",
      "rating": 5,
      "verified": true
    },
    {
      "author": "bob",
      "rating": 2,
      "verified": false
    }
  ]
}

When this document is indexed as an object field (the default), Elasticsearch internally transforms it. The flattening process converts it into something conceptually like this:

json
{
  "product_id": 1,
  "name": "Wireless Headphones",
  "reviews.author": ["alice", "bob"],
  "reviews.rating": [5, 2],
  "reviews.verified": [true, false]
}

The relationship between fields within each review object is lost. Now consider this query:

json
{
  "query": {
    "bool": {
      "must": [
        { "term": { "reviews.author": "alice" } },
        { "range": { "reviews.rating": { "gte": 4 } } }
      ]
    }
  }
}

You might expect this to return products where alice gave a 4+ star review. But with flattened object fields, this query matches because alice exists somewhere in reviews AND a 4+ rating exists somewhere in reviews — even if they're from different reviews. This is why it returns our headphones product, even though alice gave it a 5-star review and bob gave it a 2-star review. The query has no way to know that bob's 2-star review doesn't belong to alice's assessment.

In production systems handling e-commerce, review platforms, or ride-hailing (where you have drivers with multiple ratings), this leads to false positives. Users see products they shouldn't, or your analytics incorrectly correlates high ratings with verified purchases that weren't actually verified together.

Nested fields — Each object is indexed separately

To preserve the relationship between fields within an object, you use a nested field type. Nested fields treat each object in an array as a separate, hidden document within the parent document.

Map your reviews as nested in the mapping:

json
{
  "mappings": {
    "properties": {
      "product_id": { "type": "integer" },
      "name": { "type": "keyword" },
      "reviews": {
        "type": "nested",
        "properties": {
          "author": { "type": "keyword" },
          "rating": { "type": "integer" },
          "verified": { "type": "boolean" }
        }
      }
    }
  }
}

Now when you index the same document, Elasticsearch maintains the structure. Each object in the reviews array is treated as its own document, linked to the parent. The relationship between author, rating, and verified is preserved at query time.

This approach works well across real-world use cases. In a travel platform, you might have bookings with multiple passengers. In a CRM, you might have accounts with multiple contacts. In an e-commerce system with variant inventory tracking, you have SKUs with multiple warehouse locations. Nested fields let you query across these relationships accurately.

Nested queries — Required to query nested fields

You can't query nested fields with a standard bool query. You must use the nested query type. This tells Elasticsearch to evaluate the query in the context of each nested object, not the parent document.

Here's the corrected query for finding products where alice specifically gave a 4+ star review:

json
{
  "query": {
    "nested": {
      "path": "reviews",
      "query": {
        "bool": {
          "must": [
            { "term": { "reviews.author": "alice" } },
            { "range": { "reviews.rating": { "gte": 4 } } }
          ]
        }
      }
    }
  }
}

The nested wrapper tells Elasticsearch to run the inner bool query against each review object independently. Now the query correctly matches only if the same review object has both author=alice AND rating>=4.

You can also use nested queries for aggregations. If you want to bucket reviews by author and get the average rating per author:

json
{
  "query": { "match_all": {} },
  "aggs": {
    "reviews_by_author": {
      "nested": { "path": "reviews" },
      "aggs": {
        "author": {
          "terms": { "field": "reviews.author", "size": 100 },
          "aggs": {
            "avg_rating": {
              "avg": { "field": "reviews.rating" }
            }
          }
        }
      }
    }
  }
}

The outer nested aggregation scopes all sub-aggregations to the nested review objects. Without it, you'd be aggregating at the parent document level, which makes no sense for nested data.

In a trading platform where you have orders with multiple fills (partial executions), nested queries let you find orders where at least one fill happened at a specific price point, or where the total quantity of high-price fills exceeds a threshold. Without nesting, these queries become impossible to express correctly.

Cost — Nested documents increase index size

The trade-off for correctness is storage. Elasticsearch internally treats each nested object as a separate Lucene document. A parent document with 10 review objects creates 11 documents in the index (1 parent + 10 nested).

This has real implications:

  • Index size grows — For products with many reviews, your index becomes larger. Each nested object adds overhead.
  • Indexing is slower — More documents to process, invert, and store. Bulk operations take longer.
  • Query performance degrades — Nested queries must evaluate the inner query for every nested object. A product with 100 reviews means Elasticsearch evaluates your nested query 100 times for that single parent document.
  • Memory usage increases — Larger indexes and more documents mean higher memory footprint for the shard, especially during aggregations.

In a production e-commerce system with millions of products and thousands of reviews per product, this cost is unavoidable if you need accurate nested queries. But if you have arrays with thousands of objects per parent, you may need to rethink your data model entirely (consider storing nested data in a separate index and joining at query time, or using parent-child relationships instead).

We've seen teams in the ride-hailing space optimize this by setting index.mapping.nested_objects.limit to prevent documents with runaway nested counts from being indexed. In SaaS metrics platforms, we limit the number of nested datapoints per event to keep query latency reasonable.

Common mistakes

  • Forgetting the nested query wrapper — Writing a regular bool query and expecting it to work on nested fields. You'll get false positives and incorrect results silently.
  • Nesting when you don't need to — If your arrays don't need to preserve relationships (e.g., a list of tags on a product), object fields are simpler and cheaper. Reserve nested for multi-field objects.
  • Nesting deeply or widely — If a parent document has thousands of nested objects, your queries will be slow. Consider archiving, filtering at index time, or restructuring your schema.
  • Aggregating nested fields incorrectly — Forgetting the outer nested aggregation scope, or trying to "reverse" a nested aggregation with a parent aggregation without using the reverse_nested aggregation type.
  • Not accounting for cost in capacity planning — Switching from object to nested can double your index size. Factor this in when sizing infrastructure or projecting growth.

Key takeaways

  • Use object fields (the default) for simple arrays where you don't need to query relationships between fields. They're simpler and cheaper.
  • Use nested fields when you have arrays of objects and need to query or aggregate based on relationships within each object.
  • Always use a nested query to query nested fields. Regular bool queries won't work correctly and will return false positives.
  • Nested fields increase index size and query cost proportionally to the number of nested objects per parent. This is a real cost in large-scale systems.
  • Model conservatively — only nest when you need to preserve relationships. If you're unsure, start with object fields and migrate to nested if queries fail.
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles