What Inheritance Types Does JPA Support
JPA supports three entity inheritance strategies, each mapping the class hierarchy to database tables differently. The choice of strategy affects performance, normalization, and...
Overview
JPA supports three entity inheritance strategies, each mapping the class hierarchy to database tables differently. The choice of strategy affects performance, normalization, and query complexity.
Junior Level
3 Inheritance Strategies
JPA supports 3 inheritance strategies:
- SINGLE_TABLE - all classes in one table
- JOINED - each class in its own table with JOIN
- TABLE_PER_CLASS - each class in its own table (without JOIN)
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "type")
public class Payment {
@Id
private Long id;
private BigDecimal amount;
}
@Entity
@DiscriminatorValue("CARD")
public class CardPayment extends Payment {
private String cardNumber;
}
@Entity
@DiscriminatorValue("CASH")
public class CashPayment extends Payment {
private LocalDate cashDate;
}
Middle Level
SINGLE_TABLE
Table payments:
id | type | amount | card_number | cash_date
1 | CARD | 100 | 1234 | null
2 | CASH | 50 | null | 2024-01-01
Fast (no JOIN)
Simple queries
Many NULLs for subclasses
Cannot add NOT NULL constraint
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "payment_type")
public abstract class Payment { }
@Entity
@DiscriminatorValue("CARD")
public class CardPayment extends Payment { }
JOINED
Table payments:
id | amount
1 | 100
2 | 50
Table card_payments:
payment_id | card_number
1 | 1234
Table cash_payments:
payment_id | cash_date
2 | 2024-01-01
Normalized
NOT NULL constraints possible
JOIN for every query
Slower on retrieval
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public abstract class Payment { }
@Entity
@PrimaryKeyJoinColumn(name = "payment_id")
public class CardPayment extends Payment { }
TABLE_PER_CLASS
Table card_payments:
id | amount | card_number
1 | 100 | 1234
Table cash_payments:
id | amount | cash_date
2 | 50 | 2024-01-01
No NULLs, no JOIN for specific type
UNION for parent query
Complex parent queries
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Payment { }
@Entity
public class CardPayment extends Payment { }
TABLE_PER_CLASS is optional in JPA spec. Not all providers support it. Hibernate supports it, but for portability check your provider.
Common Mistakes
// SINGLE_TABLE with many subclasses
// Many fields, many NULLs -> hard to maintain
// JOINED for high-performance
// JOIN for every query -> slow
// TABLE_PER_CLASS with polymorphic queries
// UNION for SELECT FROM Payment -> slow
Senior Level
Strategy Comparison
| Strategy | INSERT | SELECT (type) | SELECT (parent) | NULLs | JOINs | Polymorphism |
|---|---|---|---|---|---|---|
| SINGLE_TABLE | Fast | Fast | Fast | Many | No | Yes |
| JOINED | Slow | Slow | Slow | No | Yes | Yes |
| TABLE_PER_CLASS | Fast | Fast (for specific subclass). Slow (UNION ALL on parent query) | UNION | No | No | Limited |
When to Use What
SINGLE_TABLE:
Small hierarchy (2-3 subclasses)
Subclass fields can be NULL
High performance needed
Polymorphic queries frequent
JOINED:
Normalization matters
Subclass fields NOT NULL
Rare polymorphic queries
Different subclasses have many fields
TABLE_PER_CLASS:
Each subclass is independent entity
Polymorphic queries rare
No common fields among subclasses
Polymorphic Queries
// SINGLE_TABLE - one query
SELECT p FROM Payment p // SELECT * FROM payments
// JOINED - JOIN all tables
SELECT p FROM Payment p // SELECT * FROM payments p
// LEFT JOIN card_payments cp ON ...
// LEFT JOIN cash_payments cash ON ...
// TABLE_PER_CLASS - UNION
SELECT p FROM Payment p // SELECT * FROM card_payments
// UNION ALL
// SELECT * FROM cash_payments
TYPE and TREAT in JPQL
// TYPE - filter by type
@Query("SELECT p FROM Payment p WHERE TYPE(p) = CardPayment")
List<CardPayment> findCardPayments();
// TREAT - cast to subclass
@Query("SELECT p FROM Payment p JOIN TREAT(p AS CardPayment) cp WHERE cp.cardNumber = :number")
List<Payment> findByCardNumber(@Param("number") String number);
Hibernate 6 Improvements
Hibernate 6:
- Improved TABLE_PER_CLASS handling
- Better JOIN optimization for JOINED
- Automatic discriminator for SINGLE_TABLE
Advanced Patterns
// Pattern: @MappedSuperclass for common fields
@MappedSuperclass
public abstract class BaseEntity {
@Id
private Long id;
private LocalDateTime createdAt;
}
@Entity
public class CardPayment extends BaseEntity { }
@Entity
public class CashPayment extends BaseEntity { }
// Each entity - separate table
// No polymorphic queries
@MappedSuperclass - NOT an entity: cannot query it, no table, cannot be used in polymorphic queries. It’s just a template for common fields. Classic interview trap: “How does @MappedSuperclass differ from @Entity?”
Best Practices
SINGLE_TABLE for simple cases (2-3 subclasses)
JOINED for normalization and NOT NULL
TABLE_PER_CLASS for independent subclasses
Avoid deep inheritance hierarchies (>3 levels). Reason: deep hierarchies complicate queries (more JOINs in JOINED, more columns in SINGLE_TABLE, more UNION branches in TABLE_PER_CLASS), make domain model harder to understand, and create cascading problems on changes.
@MappedSuperclass when polymorphism not needed
TYPE/TREAT for typed queries
SINGLE_TABLE with many subclasses
JOINED for high-performance systems
TABLE_PER_CLASS with frequent polymorphic queries
Deep inheritance hierarchies
Interview Cheat Sheet
Must know:
- 3 strategies: SINGLE_TABLE (one table), JOINED (table per class), TABLE_PER_CLASS (table per class, UNION for parent)
- SINGLE_TABLE - fast (no JOIN), but many NULLs, cannot use NOT NULL constraint
- JOINED - normalized, NOT NULL possible, but JOIN for every query - slow
- TABLE_PER_CLASS - no NULLs/JOINs for specific type, but UNION for polymorphic queries
- @MappedSuperclass - NOT an entity, no table, no polymorphic queries - template for common fields
- TYPE for filtering by type, TREAT for casting to subclass in JPQL
Frequent follow-up questions:
- Which strategy to choose? SINGLE_TABLE for 2-3 subclasses and speed; JOINED for normalization; TABLE_PER_CLASS for independent subclasses
- Why is deep hierarchy bad? More JOINs (JOINED), more columns (SINGLE_TABLE), more UNIONs (TABLE_PER_CLASS)
- @MappedSuperclass vs @Entity with inheritance? @MappedSuperclass - no polymorphic queries, no table; @Entity - has both
- Is TABLE_PER_CLASS optional in JPA spec? Yes - not all providers support it, Hibernate does
Red flags (DO NOT say):
- “SINGLE_TABLE with 10 subclasses” - many NULLs, hard to maintain
- “JOINED for high-performance system” - JOIN for every query, slow
- “TABLE_PER_CLASS with frequent polymorphic queries” - UNION ALL, even slower
- “@MappedSuperclass for polymorphic queries” - cannot query @MappedSuperclass
Related topics:
- [[26. What is JPQL and How Does It Differ from SQL]]
- [[27. What is Criteria API and When to Use It]]
- [[23. How to Properly Use @OneToMany and @ManyToOne]]
- [[24. What Are the Peculiarities of Bidirectional Relationships]]