In a JPA entity hierarchy mapped to one shared table, what is the role of the @DiscriminatorColumn and @DiscriminatorValue annotations, and what are the defaults if you declare neither?
answer
- Root gets @DiscriminatorColumn, each subclass @DiscriminatorValue
- Defaults: DTYPE, VARCHAR, entity name
- Subclass queries append WHERE dtype = '...'
- Map it in Java only as insertable=false, updatable=false
- @DiscriminatorFormula for legacy derived types
basics
~20 sThey tell Hibernate which concrete subclass a row belongs to. @DiscriminatorColumn names that column on the root entity; @DiscriminatorValue sets the value stored per subclass. Defaults: a String column named DTYPE holding the entity name. Hibernate writes it on insert and filters on it when querying a subclass.
solid answer
~50 sWith `SINGLE_TABLE` inheritance all subclasses share one table, so Hibernate needs a column that says which Java class each row is. `@DiscriminatorColumn` goes on the **root** entity and names that column, its type (`STRING`, `CHAR` or `INTEGER`) and length. `@DiscriminatorValue` goes on each concrete subclass and gives the literal stored in it. Defaults if you declare neither: a `VARCHAR` column named **`DTYPE`**, holding the **entity name** (the simple class name unless `@Entity(name=...)` overrides it). Hibernate uses it in both directions. On INSERT it writes the value. On SELECT of a subclass it appends a predicate — `WHERE dtype = 'CARD'` — and on a polymorphic select it reads the column to decide which class to instantiate for each row. Always set explicit values in production: the default couples your database contents to Java class names, so renaming or moving a class silently breaks existing rows. `@DiscriminatorFormula` covers legacy schemas where the type is derived from an expression rather than stored in a dedicated column.
code
java · 16 lines@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "payment_type", discriminatorType = DiscriminatorType.STRING, length = 20)
abstract class Payment {
@Id @GeneratedValue private Long id;
// read-only view of the Hibernate-managed column
@Column(name = "payment_type", insertable = false, updatable = false)
private String type;
}
@Entity @DiscriminatorValue("CARD")
class CardPayment extends Payment { private String cardNumber; }
@Entity @DiscriminatorValue("TRANSFER")
class BankTransfer extends Payment { private String iban; }go deeper
State that the column records which subclass a row is, that the annotation for it sits on the root, and that the defaults are a DTYPE string column holding the entity name.
Add how Hibernate uses it on insert and as a WHERE predicate for subclass queries, and why explicit stable values beat the default.
Discuss indexing the discriminator on large tables, INTEGER versus STRING discriminators, @DiscriminatorFormula for legacy schemas, and force=true when foreign rows exist.
Treat the discriminator as a published schema contract: its values outlive class names, appear in reports and downstream extracts, and constrain how the hierarchy may be refactored later.
## Why a discriminator exists When a class hierarchy is mapped with `@Inheritance(strategy = InheritanceType.SINGLE_TABLE)`, one table holds rows of `CardPayment`, `BankTransfer` and any other subclass. When Hibernate reads a row it must decide which Java class to instantiate. The **discriminator column** carries that information. (The `JOINED` strategy can also use a discriminator, though it does not need one — Hibernate can infer the type from which subclass table has a matching row, and typically synthesises a `clazz` expression in the query instead. `TABLE_PER_CLASS` has no discriminator at all, since each concrete type has its own table.) ## The annotations `@DiscriminatorColumn` is declared **once, on the root entity**: ```java @Entity @Inheritance(strategy = InheritanceType.SINGLE_TABLE) @DiscriminatorColumn(name = "payment_type", discriminatorType = DiscriminatorType.STRING, length = 20) abstract class Payment { ... } ``` Its attributes: `name` (column name, default `DTYPE`), `discriminatorType` (`STRING` default, or `CHAR`, or `INTEGER`), `length` (default 31, for `STRING`), and `columnDefinition` for raw DDL. `@DiscriminatorValue` is declared on each **concrete** subclass and on the root if it is itself instantiable: ```java @Entity @DiscriminatorValue("CARD") class CardPayment extends Payment { ... } @Entity @DiscriminatorValue("TRANSFER") class BankTransfer extends Payment { ... } ``` An abstract root needs no value, because no row can be of that type. ## The defaults Omit both and you get a `VARCHAR` column named **`DTYPE`** containing the **entity name** — the simple class name (`CardPayment`), unless you set `@Entity(name = "...")`. This is convenient in a prototype and a liability afterwards: the database now stores Java identifiers. Rename the class or move it and old rows carry the stale value; Hibernate will fail to resolve them, and you need a data migration you did not plan for. Explicit, stable, business-flavoured values (`CARD`, `TRANSFER`, or small integers) decouple the two. ## How Hibernate uses it **Insert.** The discriminator is written automatically; you neither map it as a field nor set it. **Load by id of a subclass.** `em.find(CardPayment.class, 1L)` emits roughly `SELECT ... FROM payment WHERE id = ? AND payment_type = 'CARD'` — asking for the wrong subtype returns null rather than a class-cast error. **Polymorphic query.** `SELECT p FROM Payment p` selects all rows and reads the column per row to instantiate the right class. **Subclass query.** `SELECT c FROM CardPayment c` appends `WHERE payment_type = 'CARD'` (or an `IN` list when the subclass itself has subclasses). Because that predicate appears on every subclass query, the discriminator column is a natural **index** candidate on large tables — often as the leading column of a composite index together with whatever else you filter on. ## Mapping it as a readable field The column is managed by Hibernate, so a plain `@Column` mapping of the same column conflicts. If you want to read the value in Java, map it as `@Column(name = "payment_type", insertable = false, updatable = false)`. Do not try to write it — the concrete class determines it. ## @DiscriminatorFormula For legacy schemas where the type is implied rather than stored — say a row is a card payment exactly when `card_number IS NOT NULL` — Hibernate offers `@DiscriminatorFormula("case when card_number is not null then 'CARD' else 'TRANSFER' end")` on the root instead of a column. It is Hibernate-specific and read-only in spirit: the expression must classify every row unambiguously. ## Other details worth knowing - `DiscriminatorType.INTEGER` with small numeric values stores less and indexes tighter than long strings; the trade is legibility in ad-hoc SQL. - Hibernate's `@DiscriminatorOptions(force = true)` makes it apply the discriminator predicate even for the root type — useful when the table contains rows written by other systems whose discriminator value maps to no entity, which Hibernate would otherwise choke on. - Two subclasses must not share a discriminator value; Hibernate cannot then decide which class a row is. ## What a good answer sounds like Name the purpose (which class is this row), where each annotation goes (column on the root, value on each subclass), the defaults (`DTYPE`, entity name, `VARCHAR`), the fact that Hibernate both writes it and filters on it, and the practical advice to set explicit values rather than let class names leak into the data.
- Why is relying on the default DTYPE value a risk in a long-lived system?The default stores the entity name, which is the Java class's simple name. Renaming, splitting or repackaging that class changes the value Hibernate expects while existing rows still hold the old string, so those rows can no longer be resolved. Explicit @DiscriminatorValue literals — chosen from the domain, not from the code — keep the schema independent of refactoring.
- Can you map the discriminator column to a normal entity field?Only as read-only: @Column(name = "...", insertable = false, updatable = false). Hibernate owns writing that column because the concrete class determines its value, so a writable mapping conflicts with the provider and can produce duplicate-column or inconsistent-value errors.
saying these in an interview costs you the question
- Putting @DiscriminatorColumn on each subclass instead of once on the root entity
- Thinking you must set the discriminator value in code when persisting
- Assuming the default column is named after the class or the table rather than DTYPE
- Mapping the discriminator as a writable @Column field
- Giving two subclasses the same @DiscriminatorValue