Back to CI/CD Pipeline
Q11Question 11 of 56

The default mapping type in Elasticsearch (`_doc`) and why multiple types were removed

Key Concepts
  • In older Elasticsearch, an index could contain multiple mapping types, loosely analogous to tables in a database.
  • From version 7 onward, each index has a single implicit type called _doc.
  • Multiple types were removed because all types shared one underlying Lucene index, causing field-name conflicts and sparse storage.
  • The replacement is one index per entity kind, each with its own independent field definitions.
  • If documents are variations of the same kind, use a plain field like category to distinguish them instead of types.
  • The lesson is that an abstraction which does not match its implementation is worse than no abstraction.

Definition

Multiple types pretended to be separate tables but secretly shared one storage, so Elasticsearch replaced them with a single implicit type, `_doc`, and the rule: one index, one kind of document.
  • Mapping type – a named sub-grouping of documents within one Elasticsearch index, loosely analogous to a table in a database (removed in modern ES).
  • _doc – the fixed, implicit type name in Elasticsearch 7+ that replaced multiple types; every document in an index is simply a _doc.
  • Shared Lucene index – the root cause of removal: all types in one index shared a single underlying Lucene index, so fields with the same name across types had to share one definition.
  • Mapping conflict – the error that occurs when two types define the same field name with different data types.
  • One index per entity – the modern replacement: use separate indices for genuinely different entity kinds.

Imagine National Bank of India once kept savings customers and loan customers in the same ledger book but labelled each row as either a savings row or a loan row. The trouble was that both kinds of rows had to use the exact same column definitions, so if savings used balance as money and loans used balance as remaining principal with a different format, they clashed. The bank eventually said: one ledger book holds exactly one kind of record, and if you need two kinds, keep two books.

Understanding the Concepts

What was a mapping type in older Elasticsearch?

In Elasticsearch versions before six, you created an index and could define several types within it, for example a customer type and a transaction type. Each type had its own mapping describing its fields. People reached for this because it looked like a relational database where one database holds many tables.

BASH
# Old-style ES 5.x: two types in one index (DEPRECATED)
curl -X PUT "http://localhost:9200/banking/customer/1" \
  -d '{"name":"Rajesh","balance":500000}'

curl -X PUT "http://localhost:9200/banking/transaction/1" \
  -d '{"amount":25000,"type":"TRANSFER"}'

Why were multiple types removed?

The problem was that this analogy was false under the hood. All types within one index were stored in the same Apache Lucene index. Lucene has no concept of types; it only sees fields. So if the customer type and the transaction type both had a field called amount, they were the same Lucene field and had to share one definition.

BASH
# This caused a mapping conflict in ES 5.x:
# customer: "amount" as text (credit limit description)
# transaction: "amount" as long (money value)
# ERROR: mapper [amount] cannot be changed from type [text] to [long]
DIAGRAM
Problems caused by shared fields:

1. Mapping conflicts: same field name, different types → error
2. Sparse storage: customer docs leave transaction fields blank
3. Poor compression: many null fields waste space
4. Confusing behavior: developers assume isolation that doesn't exist

What is the _doc type?

Starting in version six, Elasticsearch limited each index to a single type. In version seven, that type became the fixed implicit value _doc. The document APIs use the _doc endpoint, and you treat the type as a vestigial constant.

BASH
# Modern ES 7+: single _doc type
curl -X PUT "http://localhost:9200/customers/_doc/1" \
  -H "Content-Type: application/json" \
  -d '{"name":"Rajesh","balance":500000}'

curl -X PUT "http://localhost:9200/transactions/_doc/1" \
  -H "Content-Type: application/json" \
  -d '{"amount":25000,"type":"TRANSFER","status":"COMPLETED"}'

How do you migrate from multiple types to separate indices?

If you previously used two types in one index, you now use two separate indices, one per entity kind. Separate indices are genuinely independent and each has its own field definitions.

BASH
# Old: one index with two types
# banking/customer and banking/transaction

# New: two separate indices
curl -X PUT "http://localhost:9200/customers" \
  -H "Content-Type: application/json" \
  -d '{
    "mappings": {
      "properties": {
        "name": {"type": "keyword"},
        "balance": {"type": "scaled_float", "scaling_factor": 100}
      }
    }
  }'

curl -X PUT "http://localhost:9200/transactions" \
  -H "Content-Type: application/json" \
  -d '{
    "mappings": {
      "properties": {
        "amount": {"type": "long"},
        "status": {"type": "keyword"},
        "timestamp": {"type": "date"}
      }
    }
  }'

What if documents are variations of the same kind?

If documents are variations of the same entity, keep them in one index and add a plain field to distinguish them.

BASH
# One index for all account types, distinguished by a field
curl -X PUT "http://localhost:9200/accounts/_doc/1" \
  -H "Content-Type: application/json" \
  -d '{
    "category": "savings",
    "accountNumber": "SAV001",
    "balance": 250000,
    "interestRate": 4.5
  }'

curl -X PUT "http://localhost:9200/accounts/_doc/2" \
  -H "Content-Type: application/json" \
  -d '{
    "category": "current",
    "accountNumber": "CUR001",
    "balance": 1000000,
    "overdraftLimit": 500000
  }'

# Filter by category at query time
curl -X GET "http://localhost:9200/accounts/_search" \
  -H "Content-Type: application/json" \
  -d '{"query":{"term":{"category":"savings"}}}'

How did the version timeline progress?

| ES Version | Behavior |
|---|---|
| 5.x | Multiple types per index allowed |
| 6.x | Limited to single type per index |
| 7.x | Type defaults to _doc, type in URL deprecated |
| 8.x | Type references removed from APIs |

BASH
# ES 5.x URL: index/type/id
curl -X GET "http://localhost:9200/banking/customer/1"

# ES 7.x URL: index/_doc/id
curl -X GET "http://localhost:9200/customers/_doc/1"

# ES 8.x URL: index/_doc/id (type parameter rejected)
curl -X GET "http://localhost:9200/customers/_doc/1"

What are common mistakes?

The most common mistake is cramming different entity types into one index to save on index overhead, recreating the problems multiple types caused.

BASH
# WRONG: customers and transactions in the same index
curl -X PUT "http://localhost:9200/banking/_doc/cust_1" \
  -d '{"entity":"customer","name":"Rajesh","balance":500000}'
curl -X PUT "http://localhost:9200/banking/_doc/txn_1" \
  -d '{"entity":"transaction","amount":25000}'
# "balance" and "amount" may conflict or waste space

# CORRECT: separate indices
curl -X PUT "http://localhost:9200/customers/_doc/1" \
  -d '{"name":"Rajesh","balance":500000}'
curl -X PUT "http://localhost:9200/transactions/_doc/1" \
  -d '{"amount":25000}'

What should a senior engineer know?

A senior engineer understands that this change reflects a broader principle: an abstraction that does not match its implementation is worse than no abstraction. Mapping types promised relational-style isolation but delivered shared Lucene fields, and that mismatch produced bugs for years. Removing them simplified the mental model to "an index is a collection of similar documents," which aligns with how Lucene actually works.

JAVA
package com.bank.config;

import org.springframework.data.elasticsearch.client.ClientConfiguration;
import org.springframework.data.elasticsearch.client.elc.ElasticsearchConfiguration;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ElasticsearchConfig extends ElasticsearchConfiguration {

    @Override
    public ClientConfiguration clientConfiguration() {
        return ClientConfiguration.builder()
            .connectedTo("elasticsearch:9200")
            .build();
    }
    // Modern Spring Data ES uses index-per-entity automatically
    // No type configuration needed
}

Visual Overview

DIAGRAM
  Old Elasticsearch (types)            Modern Elasticsearch (_doc)
  ─────────────────────────            ────────────────────────────
  Index: banking                       Index: customers
  ┌─────────────────────────┐          ┌─────────────────────────┐
  │ Type: customer           │          │ _doc                    │
  │   name: keyword          │          │   name: keyword         │
  │   balance: long     ←conflict→     │   balance: scaled_float │
  │ Type: transaction        │          └─────────────────────────┘
  │   amount: long           │
  │   balance: text     ←conflict→     Index: transactions
  └─────────────────────────┘          ┌─────────────────────────┐
  Shared Lucene index!                 │ _doc                    │
  Fields with same name clash          │   amount: long          │
                                       │   status: keyword       │
                                       └─────────────────────────┘
                                       Independent Lucene indices!
                                       No field conflicts

Key Takeaway

In older Elasticsearch, an index could contain multiple mapping types that people treated like tables in a database. The problem was that all types shared one underlying Lucene index, so fields with the same name across types had to share one definition, causing mapping conflicts and sparse storage. From version seven onward, each index has a single implicit type called _doc, and you never create meaningful type names.

The modern replacement is simple: use separate indices for genuinely different entity kinds, and use a plain field like category to distinguish variations within one index. At National Bank of India, splitting customers and transactions into their own indices eliminated the field conflicts that had plagued the old combined index.

The broader lesson is that an abstraction which does not match its implementation is worse than no abstraction. Removing mapping types gave Elasticsearch a mental model that finally matches how Lucene works.

Remember one sentence: one index holds one kind of document, the type is always _doc, and if you need two kinds, use two indices.

---

Share
Use ← → arrow keys to navigate