Course topics

By WebNest Studio Engineering Team

Advanced Java Tutorial

Hibernate Inheritance Mapping Strategies

Java supports class inheritance, but relational tables don't have a native concept of "subclass." Hibernate offers three strategies for mapping a Java inheritance hierarchy onto tables, each with a different trade-off between query simplicity, storage normalization, and performance.

Table Per Hierarchy (Single Table)

The default strategy: one table holds every class in the hierarchy, with a discriminator column recording which subclass each row represents. It is the fastest option — reading or querying across the whole hierarchy never requires a join — but columns unique to a particular subclass must be nullable, since a row for one subclass leaves the other subclasses' columns empty.

Table Per Concrete Class

Each concrete subclass gets its own table containing all of its fields, including ones inherited from the parent — nothing is shared or joined. This avoids nullable columns entirely, but querying across the whole hierarchy (e.g. "all Payments regardless of subtype") requires a UNION across every subclass table, and there is no shared table to hold a truly common identity.

Table Per Subclass (Joined)

Each class — including the abstract parent — gets its own table holding only its own fields, linked to the parent's table by a shared primary key (a foreign key that is also the primary key). This is the most normalized option and avoids nullable columns, but reading a full subclass object requires a join across as many tables as there are levels in the hierarchy, which can be slower for deep hierarchies or wide queries across the whole tree.

Example

Table Per Hierarchy with a discriminator column

Java
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "payment_type")
public abstract class Payment {
    @Id @GeneratedValue private long id;
    private double amount;
}

@Entity
@DiscriminatorValue("CREDIT_CARD")
public class CreditCardPayment extends Payment {
    private String cardNumber;
}

@Entity
@DiscriminatorValue("UPI")
public class UpiPayment extends Payment {
    private String upiId;
}

// Table Per Subclass instead, just change the strategy:
// @Inheritance(strategy = InheritanceType.JOINED)

// Table Per Concrete Class instead:
// @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
Output
Hibernate: create table payment (payment_type varchar(31) not null, id bigint not null, amount double, card_number varchar(255), upi_id varchar(255), primary key (id))

Common Mistakes

  • Choosing Table Per Hierarchy for a hierarchy with many subclass-specific, mutually exclusive fields, resulting in a very wide table full of nulls.
  • Choosing Table Per Concrete Class and then needing hierarchy-wide queries, which forces expensive UNION operations across every subclass table.
  • Forgetting @DiscriminatorValue, leaving Hibernate to fall back on the class name, which can break if classes are renamed later.
  • Assuming Table Per Subclass has no cost — the joins it requires do add query overhead compared to a single-table read.

Exercise

Using the lesson's Table Per Hierarchy with a discriminator column example, create a small implementation or design sketch that demonstrates Table Per Hierarchy (SINGLE_TABLE): one table, fastest reads, nullable subclass-specific columns.. Add a test case or scenario that would catch Choosing Table Per Hierarchy for a hierarchy with many subclass-specific, mutually exclusive fields, resulting in a very wide table full of nulls., and describe the expected result.

Show hints
  • Start with Table Per Hierarchy (Single Table) and compare your approach with Table Per Hierarchy with a discriminator column. Identify the decision or behavior the lesson recommends.
  • Walk through your scenario step by step. Confirm that it follows Table Per Hierarchy (SINGLE_TABLE): one table, fastest reads, nullable subclass-specific columns. and that it would catch the failure described by Choosing Table Per Hierarchy for a hierarchy with many subclass-specific, mutually exclusive fields, resulting in a very wide table full of nulls..

Quick quiz

1. Which choice is a mistake to avoid in Hibernate Inheritance Mapping Strategies?

  • A. Table Per Hierarchy (SINGLE_TABLE): one table, fastest reads, nullable subclass-specific columns.
  • B. Choosing Table Per Hierarchy for a hierarchy with many subclass-specific, mutually exclusive fields, resulting in a very wide table full of nulls.
  • C. Choosing Table Per Concrete Class and then needing hierarchy-wide queries, which forces expensive UNION operations across every subclass table.
Show answer

B. Avoid this approach: Choosing Table Per Hierarchy for a hierarchy with many subclass-specific, mutually exclusive fields, resulting in a very wide table full of nulls. The lesson recommends: Table Per Hierarchy (SINGLE_TABLE): one table, fastest reads, nullable subclass-specific columns.

Interview questions

How would you apply the guidance in Table Per Hierarchy (Single Table) to a real task, and what failure would you guard against?

Show answer

Table Per Hierarchy (SINGLE_TABLE): one table, fastest reads, nullable subclass-specific columns. Apply this in Table Per Hierarchy (Single Table); the example Table Per Hierarchy with a discriminator column shows one way to do it. Avoid Choosing Table Per Hierarchy for a hierarchy with many subclass-specific, mutually exclusive fields, resulting in a very wide table full of nulls. and explain how your implementation or design prevents that failure.

Key Points to Remember

  • Table Per Hierarchy (SINGLE_TABLE): one table, fastest reads, nullable subclass-specific columns.
  • Table Per Concrete Class (TABLE_PER_CLASS): one table per subclass, no nulls, but hierarchy-wide queries need UNIONs.
  • Table Per Subclass (JOINED): normalized, no nulls, but reads require joins across parent/child tables.
  • Pick the strategy based on how wide/shared the hierarchy's fields are and how often you query across the whole hierarchy.

Practice in the playground

Write and run your own code for this topic, or start from the exercise or an example. Runs plain Java 17. Frameworks such as Spring, and databases, are not available here. Your code for each topic is kept in this browser.

Run the examples

Change an input, predict the result, then compare it with the output. Explain why the result changes.

These examples need a JDK project with their libraries on your computer, so they can be copied but not run here. Codelab runs Python, FastAPI backends with SQLite, plain Java 17 and HTML/CSS/JavaScript.

WebNest Studio

Written by

WebNest Studio Engineering Team

Practical lessons from developers working with Java, Python, React, databases and production systems.

Led by Vansh Duggal, Founder, WebNest Studio · Full-stack & software engineering