In JPA, what is the difference between putting @MappedSuperclass on a shared base class and making that base class an @Entity with @Inheritance, and how do you decide which one a shared base belongs in?
answer
- @MappedSuperclass = template, no table, no type
- Cannot query it, cannot associate with it
- Entity inheritance = queryable, referenceable, polymorphic
- Audit/id/version columns -> MappedSuperclass
- Not the same as TABLE_PER_CLASS (that keeps a queryable base)
basics
~20 s@MappedSuperclass only shares column mappings: it has no table, cannot be queried, and nothing can reference it as an association — each subclass table simply gets copies of its columns. Entity inheritance makes the base a real queryable type with polymorphic queries and associations. Choose by whether you need polymorphism.
solid answer
~50 s`@MappedSuperclass` is **mapping reuse, not a type**. The class has no table, no rows and no identity of its own; its `@Id`, `@Column`, `@Version` and lifecycle-callback declarations are simply inherited into each subclass, whose own table gains copies of those columns. You cannot write `SELECT b FROM BaseAudit b`, you cannot declare `@ManyToOne BaseAudit`, and `em.find(BaseAudit.class, id)` is meaningless. It is also not required to be abstract, but it is never persistable itself. Entity inheritance (`@Entity` + `@Inheritance`) makes the base a **first-class persistent type**: it participates in JPQL polymorphically, other entities can hold associations to it, and rows of any subclass are reachable through it. The deciding question is therefore: *does the application ever need to treat the subclasses uniformly in a query or an association?* If yes — a `Payment` list mixing cards and transfers — use entity inheritance. If the base only factors out technical columns (`id`, `createdAt`, `createdBy`, `version`) with no domain meaning, use `@MappedSuperclass` and avoid the schema and query cost entirely.
code
java · 13 lines@MappedSuperclass
public abstract class Auditable {
@Id @GeneratedValue private Long id;
@Column(nullable = false, updatable = false) private Instant createdAt;
@Version private long version;
@PrePersist void stamp() { createdAt = Instant.now(); } // applies to subclasses
}
@Entity class Invoice extends Auditable { private String number; }
// em.createQuery("select a from Auditable a", Auditable.class); // ILLEGAL
// @ManyToOne private Auditable owner; // ILLEGALgo deeper
Say that @MappedSuperclass has no table and cannot be queried, while an @Entity base can be, and that audit fields belong in the former.
Explain the resulting schema for each, the inheritance of callbacks and @AttributeOverride, and the polymorphic-need decision rule.
Distinguish it sharply from TABLE_PER_CLASS, discuss the migration cost of changing your mind, and describe when one-query-per-subtype code proves you needed a real hierarchy.
Treat the base class as an API decision: making a type polymorphic commits you to a schema strategy and to every consumer being able to reference it, which is far harder to withdraw than to add.
## Two very different mechanisms Both put common state in a shared Java base class, and that surface similarity is why the question is asked. Underneath they are unrelated. ### @MappedSuperclass — a mapping template ```java @MappedSuperclass public abstract class Auditable { @Id @GeneratedValue private Long id; @Column(nullable = false, updatable = false) private Instant createdAt; private String createdBy; @Version private long version; } @Entity class Invoice extends Auditable { private String number; } @Entity class Customer extends Auditable { private String name; } ``` Hibernate treats `Auditable` as a set of instructions applied to every subclass. Schema produced: ``` invoice (id, created_at, created_by, version, number) customer(id, created_at, created_by, version, name) ``` Each concrete table simply carries its own copy of the inherited columns. There is no `auditable` table, no discriminator, no join, and — crucially — **no persistent type called `Auditable`**. Consequences: - `SELECT a FROM Auditable a` is invalid; `Auditable` is not in the JPQL type system. - `@ManyToOne private Auditable owner;` is illegal — you cannot associate with it. - `em.find(Auditable.class, 1L)` is meaningless. - Subclasses may override inherited mappings with `@AttributeOverride` / `@AssociationOverride`. - Lifecycle callbacks (`@PrePersist`, `@PreUpdate`) declared on it **do** apply to subclasses — this is why it is the standard home for auditing fields. - It costs nothing at query time: each entity is a plain single-table entity. ### Entity inheritance — a real polymorphic type ```java @Entity @Inheritance(strategy = InheritanceType.SINGLE_TABLE) public abstract class Payment { @Id @GeneratedValue private Long id; private BigDecimal amount; } @Entity class CardPayment extends Payment { private String cardNumber; } @Entity class BankTransfer extends Payment { private String iban; } ``` Now `Payment` is a queryable, referenceable type: - `SELECT p FROM Payment p WHERE p.amount > 100` returns a mixed list of concrete subclasses. - `@ManyToOne private Payment payment;` works, and Hibernate resolves the concrete type on load. - `em.find(Payment.class, 42L)` returns whichever subclass row 42 is. - `TYPE(p) = CardPayment` and `TREAT(p AS CardPayment)` are available in JPQL. The price is whichever strategy's cost you signed up for: nullable columns (SINGLE_TABLE), joins (JOINED) or unions (TABLE_PER_CLASS), plus polymorphic query cost that grows with the hierarchy. ## The decision rule Ask: **does anything outside the class need to treat the subtypes as one type?** Use `@MappedSuperclass` when the base is *technical* — identifiers, audit stamps, version columns, soft-delete flags, tenant ids. Nobody wants to query "all auditable things"; the base exists so you do not retype five annotations per entity. Use entity inheritance when the base is a *domain concept* — `Payment`, `Document`, `Account` — and the application legitimately lists, references or dispatches on it. If you find yourself running one query per subtype and merging the results in Java, that is the signal you needed a real hierarchy. A useful sanity check: if the base type would never appear as the type of a variable, a method parameter, or a collection element outside its own subclasses, it does not need to be an entity. ## Mistakes this question is testing for - Believing `@MappedSuperclass` produces `TABLE_PER_CLASS`. They look alike in the schema — duplicated columns per concrete table — but `TABLE_PER_CLASS` keeps the base as a queryable entity backed by a `UNION ALL` view, requires hierarchy-wide unique identifiers, and forbids `IDENTITY` generation. `@MappedSuperclass` has none of that machinery because there is no base type at all. - Reaching for entity inheritance to share audit columns, thereby importing polymorphic query cost and a schema decision for zero benefit. - Forgetting that a `@MappedSuperclass` may itself extend another `@MappedSuperclass`, and that a `@MappedSuperclass` can sit *above* an entity hierarchy root — the two mechanisms compose. - Assuming an ordinary (unannotated) Java superclass works: its fields are simply **not** persisted unless the class is `@MappedSuperclass` or `@Entity`. That silent data loss is a classic bug. ## Migration between them Going from `@MappedSuperclass` to entity inheritance is a schema change, not just an annotation change: with SINGLE_TABLE you must merge the per-entity tables into one and add a discriminator; with JOINED you must extract a base table and turn each subclass primary key into a foreign key. It is doable but not free, which argues for thinking about polymorphic need up front — while also arguing against speculatively making everything an entity hierarchy "just in case".
- Isn't @MappedSuperclass just another name for TABLE_PER_CLASS, since both duplicate columns into each concrete table?The schemas look similar, but TABLE_PER_CLASS keeps the base as a real entity: you can query it polymorphically (Hibernate builds a UNION ALL over the concrete tables), and that forces identifiers to be unique across the hierarchy, ruling out IDENTITY generation. @MappedSuperclass has no base type, so there is nothing to query, nothing to reference, and no cross-table identifier constraint.
- Do @PrePersist and similar callbacks declared on a @MappedSuperclass run for its subclasses?Yes — lifecycle callbacks and listener declarations are inherited along with the mappings, which is exactly why audit base classes are conventionally @MappedSuperclass. A subclass may override a callback method, and only the overriding version then runs for that entity.
@MappedSuperclass is a document template: every finished document contains its boilerplate, but there is no filing cabinet drawer for 'the template'. Entity inheritance is a real category in the filing system — you can pull the whole 'Payments' drawer and see cards and transfers together.
saying these in an interview costs you the question
- "@MappedSuperclass and TABLE_PER_CLASS are the same thing"
- Trying to query or associate with a @MappedSuperclass type
- Using entity inheritance purely to share id/createdAt/version columns
- Assuming a plain unannotated Java superclass has its fields persisted
- Believing switching between the two is only an annotation change, not a schema migration