<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Zay's Engineering Notes]]></title><description><![CDATA[Practical engineering notes from Zay Maung Maung Myint on Java, Spring Boot, JPA, databases, React JS and building reliable backend systems.]]></description><link>https://zay-engineering-notes.hashnode.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/6358a115ff3bb3047322d0b5/d2adf764-3742-4174-a43b-f0f88f9c6379.png</url><title>Zay&apos;s Engineering Notes</title><link>https://zay-engineering-notes.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Mon, 05 Oct 2026 07:32:18 GMT</lastBuildDate><atom:link href="https://zay-engineering-notes.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Understanding JPA JOINED Inheritance: From Java Subtypes to Database Tables
]]></title><description><![CDATA[The Domain Model
An employee enters a building every day using an access card. A visitor might enter the same building using a temporary credential.
Both people have a name, an identity, and authentic]]></description><link>https://zay-engineering-notes.hashnode.dev/understanding-jpa-joined-inheritance</link><guid isPermaLink="true">https://zay-engineering-notes.hashnode.dev/understanding-jpa-joined-inheritance</guid><category><![CDATA[jpa]]></category><category><![CDATA[Java]]></category><category><![CDATA[spring-boot]]></category><category><![CDATA[database]]></category><category><![CDATA[hibernate]]></category><dc:creator><![CDATA[Zay Maung Maung Myint]]></dc:creator><pubDate>Sat, 26 Sep 2026 13:04:51 GMT</pubDate><content:encoded><![CDATA[<img src="https://cdn.hashnode.com/uploads/covers/6358a115ff3bb3047322d0b5/48f5cda1-7258-43f0-ba90-372e387eca12.png" alt="" style="display:block;margin:0 auto" />

<h2><strong>The Domain Model</strong></h2>
<p>An employee enters a building every day using an access card. A visitor might enter the same building using a temporary credential.</p>
<p>Both people have a name, an identity, and authentication information. However, they do not have the same business data. An employee has an employee number and company email. A visitor has a visitor number and an external company.</p>
<blockquote>
<p><mark class="bg-yellow-200 dark:bg-yellow-500/30">Java represents this naturally with inheritance:</mark></p>
</blockquote>
<pre><code class="language-plaintext">AccessUser
├── Employee
└── Visitor
</code></pre>
<blockquote>
<p><em><mark class="bg-yellow-200 dark:bg-yellow-500/30">But relational databases do not understand classes, inheritance, or subtypes. They understand tables, columns, primary keys, foreign keys, and joins.</mark></em></p>
</blockquote>
<p><strong>This creates an important question:</strong></p>
<blockquote>
<p><em><mark class="bg-yellow-200 dark:bg-yellow-500/30">How does Hibernate store one Java subtype when part of its data comes from a parent class and the rest comes from the subtype?</mark></em></p>
</blockquote>
<p>JPA’s <code>JOINED</code> inheritance strategy answers this.</p>
<p>This note focuses exclusively on <code>InheritanceType.JOINED</code>. JPA also supports <code>SINGLE_TABLE</code> and <code>TABLE_PER_CLASS</code>, while <code>@MappedSuperclass</code> is commonly discussed as another inheritance-mapping approach. We will leave those alternatives for separate articles.</p>
<hr />
<p>Consider a simplified access-control system.</p>
<p>Every person who receives access credentials is represented as an <code>AccessUser</code>. <strong>Employees</strong> and <strong>Visitors</strong> are specialized types of access users.</p>
<p>The parent class contains information shared by every subtype:</p>
<pre><code class="language-java">public abstract class AccessUser {

    protected Long id;
    protected String firstName;
    protected String lastName;
    protected String credential;
}
</code></pre>
<p>An employee adds employee-specific information:</p>
<pre><code class="language-java">public class Employee extends AccessUser {

    private String employeeId;
    private String email;
}
</code></pre>
<p>A visitor adds visitor-specific information:</p>
<pre><code class="language-java">public class Visitor extends AccessUser {

    private String visitorId;
    private String company;
}
</code></pre>
<p>The entity hierarchy looks like this:</p>
<p>This hierarchy represents a genuine subtype relationship:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6358a115ff3bb3047322d0b5/cd04f1e4-6be8-42ae-9001-a1e54452faec.png" alt="" style="display:block;margin:0 auto" />

<pre><code class="language-sql">An Employee is an AccessUser.
A Visitor is an AccessUser.
</code></pre>
<p>The next challenge is mapping that object model to relational tables.</p>
<hr />
<h2><strong>Mapping the Hierarchy with JOINED</strong></h2>
<p>The inheritance strategy is declared on the root entity:</p>
<pre><code class="language-java">@Entity
@Table(name = "access_users")
@Inheritance(strategy = InheritanceType.JOINED)
public abstract class AccessUser {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    protected Long id;

    @Column(nullable = false)
    protected String firstName;

    @Column(nullable = false)
    protected String lastName;

    protected String credential;

    protected AccessUser() {
    }
}
</code></pre>
<blockquote>
<p>The key annotation is:</p>
</blockquote>
<pre><code class="language-javascript">@Inheritance(strategy = InheritanceType.JOINED)
</code></pre>
<p>It tells the JPA provider to:</p>
<ol>
<li><p><em>Use one table for the root entity.</em></p>
</li>
<li><p><em>Use a separate table for each managed subtype.</em></p>
</li>
<li><p><em>Store common fields in the root table.</em></p>
</li>
<li><p><em>Store subtype-specific fields in the subtype table.</em></p>
</li>
<li><p><em>Connect the parent and subtype rows using the same primary key.</em></p>
</li>
</ol>
<p>Declare the strategy only on the root of the hierarchy. It does not need to be repeated on every subtype.</p>
<p>The concrete subtypes still need to be JPA entities:</p>
<pre><code class="language-java">@Entity
@Table(name = "employees")
public class Employee extends AccessUser {

    @Column(nullable = false, unique = true)
    private String employeeId;

    @Column(nullable = false)
    private String email;

    protected Employee() {
    }
}
</code></pre>
<pre><code class="language-java">@Entity
@Table(name = "visitors")
public class Visitor extends AccessUser {

    @Column(nullable = false, unique = true)
    private String visitorId;

    private String company;

    protected Visitor() {
    }
}
</code></pre>
<p>Each subtype uses <code>extends AccessUser</code> for Java inheritance and <code>@Entity</code> for JPA persistence.</p>
<hr />
<h2><strong>The Resulting Database Tables</strong></h2>
<p>The mapping produces one parent table and one table for each subtype:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6358a115ff3bb3047322d0b5/7c1c92cd-ef56-4b3a-a6d4-475657aa78ce.png" alt="" style="display:block;margin:0 auto" />

<p>A simplified PostgreSQL schema could look like this:</p>
<pre><code class="language-sql">CREATE TABLE access_users (
    id          BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
    first_name  TEXT NOT NULL,
    last_name   TEXT NOT NULL,
    credential  TEXT
);

CREATE TABLE employees (
    id           BIGINT PRIMARY KEY,
    employee_id  TEXT NOT NULL UNIQUE,
    email        TEXT NOT NULL,

    CONSTRAINT fk_employees_access_user
        FOREIGN KEY (id) REFERENCES access_users (id)
);

CREATE TABLE visitors (
    id               BIGINT PRIMARY KEY,
    visitor_id       TEXT NOT NULL UNIQUE,
    company          TEXT,

    CONSTRAINT fk_visitors_access_user
        FOREIGN KEY (id) REFERENCES access_users (id)
);
</code></pre>
<p>The most important part of this structure is the subtype ID:</p>
<pre><code class="language-plaintext">employees.id → access_users.id
visitors.id  → access_users.id
</code></pre>
<p>The subtype table’s <code>id</code> is both:</p>
<ul>
<li><p>its primary key;</p>
</li>
<li><p>a foreign key to <code>access_user.id</code></p>
</li>
</ul>
<p>Suppose both tables contain a row with ID <code>42</code>:</p>
<pre><code class="language-javascript">access_users
────────────────────────────────────────────
id    first_name    last_name    credential
42    Ethan         Zay          CARD-1007

visitors
────────────────────────────────────
id    visitor_id    company
42    V99881        Example Ltd
</code></pre>
<p>These are not two independent business objects. Together, they represent one <code>Visitor</code>:</p>
<pre><code class="language-sql">access_users row 42
        +
visitors row 42
        ↓
one Visitor entity
</code></pre>
<p>This is the central idea behind <code>JOINED</code> inheritance.</p>
<hr />
<h2><strong>What Happens When Hibernate Saves a Visitor?</strong></h2>
<p>Consider the following object:</p>
<pre><code class="language-java">Visitor visitor = new Visitor();
visitor.setFirstName("Ethan");
visitor.setLastName("Zay");
visitor.setCredential("CARD-1007");
visitor.setVisitorId("V99881");
visitor.setCompany("Example Ltd");

visitorRepository.save(visitor);
</code></pre>
<p>Hibernate needs to persist both the inherited state and the visitor-specific state.</p>
<p>It first inserts the common fields into <code>access_users</code>:</p>
<pre><code class="language-sql">INSERT INTO access_users (
    first_name,
    last_name,
    credential
)
VALUES (
    'Ethan',
    'Zay',
    'CARD-1007'
)
RETURNING id;
</code></pre>
<p>Assume that the database generates ID <code>42</code>. Hibernate then uses that ID when inserting the subtype fields:</p>
<pre><code class="language-sql">INSERT INTO visitors (
    id,
    visitor_id,
    company
)
VALUES (
    42,
    'V99881',
    'Example Ltd'
);
</code></pre>
<p>One Java object therefore produces two database rows:</p>
<pre><code class="language-sql">Visitor object
    │
    ├── common state ────────&gt; access_users row 42
    │
    └── visitor state ───────&gt; visitors row 42
</code></pre>
<p>Both inserts normally execute within the same transaction. If one insert fails, the transaction should roll back instead of leaving an incomplete entity behind.</p>
<h2><strong>What Happens When Hibernate Loads a Visitor?</strong></h2>
<p>When the application loads visitor <code>42</code>, Hibernate needs information from both tables.</p>
<p>The exact generated SQL depends on the Hibernate version and database dialect, but its logical form is:</p>
<pre><code class="language-sql">SELECT
    au.id,
    au.first_name,
    au.last_name,
    au.credential,
    v.visitor_id,
    v.company
FROM access_users au
JOIN visitors v ON v.id = au.id
WHERE au.id = 42;
</code></pre>
<p>Hibernate combines the columns into one Java object:</p>
<pre><code class="language-json">Visitor {
    id = 42,
    firstName = "Ethan",
    lastName = "Zay",
    credential = "CARD-1007",
    visitorId = "V99881",
    company = "Example Ltd"
}
</code></pre>
<p>The application works with one object even though its state is stored across two tables.</p>
<p>This join is both the mechanism and the principal cost of <code>JOINED</code> inheritance. Every subtype read requires Hibernate to join the subtype table with its parent table.</p>
<p>Because the root is also an entity, a query against <code>AccessUser</code> can return both <code>Employee</code> and <code>Visitor</code> instances. Hibernate may need additional joins to determine the concrete subtype, so parent-level queries become more expensive as a hierarchy grows.</p>
<hr />
<h2><strong>Benefits of JOINED Inheritance</strong></h2>
<p>The <code>JOINED</code> strategy provides several useful properties.</p>
<h3><strong>Normalized tables</strong></h3>
<p>Common fields are stored once in the parent table instead of being repeated in every subtype table.</p>
<h3><strong>Strong subtype constraints</strong></h3>
<p>Subtype-specific columns can have appropriate database constraints:</p>
<pre><code class="language-sql">visitor_id     TEXT NOT NULL
employee_id    TEXT NOT NULL UNIQUE
</code></pre>
<p>This is harder to enforce when unrelated subtype columns share one large table.</p>
<h3><strong>Shared identity</strong></h3>
<p>Every employee and visitor has a common <code>AccessUser</code> identity. Other parts of the system can reference that shared identity when the domain requires it.</p>
<h3><strong>Polymorphic entity model</strong></h3>
<p>Application code can operate on the parent type when it does not need subtype-specific behavior.</p>
<p>The Jakarta Persistence specification describes <code>JOINED</code> as a well-normalized strategy with support for polymorphic relationships.</p>
<h2><strong>Trade-Offs to Consider</strong></h2>
<p><code>JOINED</code> is not automatically the best strategy for every hierarchy.</p>
<h3>Additional joins</h3>
<p>Loading a subtype requires at least one join with its parent table.</p>
<h3>Queries across the hierarchy</h3>
<p>Queries against the parent entity may need to inspect several subtype tables. A hierarchy with many subtypes can generate large SQL statements.</p>
<h3>Multi-table writes</h3>
<p>Creating or deleting a subtype affects more than one table. Updating fields can also target different tables:</p>
<pre><code class="language-sql">Changing firstName → UPDATE access_users
Changing company   → UPDATE visitors
</code></pre>
<h3>More complex schema changes</h3>
<p>The database must maintain parent-child foreign-key integrity. Migrations and manual data operations need to respect that relationship.</p>
<p>The Java model may look simple, but developers should still inspect the SQL generated for important production queries.</p>
<h2><strong>Common implementation mistakes</strong></h2>
<h3>Missing <code>@Entity</code> on a subtype</h3>
<p>This is incomplete:</p>
<pre><code class="language-java">@Table(name = "visitors")
public class Visitor extends AccessUser {
}
</code></pre>
<p><code>@Table</code> specifies a table mapping, but it does not register the class as an entity.</p>
<p>The correct mapping is:</p>
<pre><code class="language-java">@Entity
@Table(name = "visitors")
public class Visitor extends AccessUser {
}
</code></pre>
<h3>Declaring a Separate ID in the subtype</h3>
<p>The subtype should inherit the parent ID. It should not introduce an unrelated primary key:</p>
<pre><code class="language-java">@Entity
public class Visitor extends AccessUser {

    // Do not declare another @Id here.
}
</code></pre>
<p>The shared ID is what connects the parent and subtype rows.</p>
<h3>Using inheritance only to reuse fields</h3>
<p>Two entities sharing <code>createdAt</code> and <code>updatedAt</code> does not necessarily mean they belong to the same entity hierarchy.</p>
<p>Use inheritance when the subtype truly represents the parent concept:</p>
<pre><code class="language-sql">Visitor is an AccessUser → valid subtype
</code></pre>
<hr />
<h2><strong>When JOINED is a good fit?</strong></h2>
<p><strong>Consider</strong> <code>JOINED</code> <strong>when:</strong></p>
<ul>
<li><p>the classes have a genuine “is-a” relationship;</p>
</li>
<li><p>the subtypes share meaningful business identity;</p>
</li>
<li><p>normalized tables are important;</p>
</li>
<li><p>subtype-specific constraints matter;</p>
</li>
<li><p>polymorphic behavior is required;</p>
</li>
<li><p>the hierarchy contains a controlled number of subtypes;</p>
</li>
<li><p>the cost of joins is acceptable.</p>
</li>
</ul>
<p><strong>Reconsider it when:</strong></p>
<ul>
<li><p>the classes only share technical fields;</p>
</li>
<li><p>the hierarchy contains many levels or subtypes;</p>
</li>
<li><p>most queries process the entire hierarchy at high volume;</p>
</li>
<li><p>query latency is more important than normalization;</p>
</li>
<li><p>the database structure would be simpler without entity inheritance.</p>
</li>
</ul>
<h2><strong>Key takeaway</strong></h2>
<p>JPA <code>JOINED</code> inheritance maps one Java entity across a parent table and a subtype table.</p>
<p>The parent table stores shared state. The subtype table stores specialized state. The same primary key connects both rows:</p>
<pre><code class="language-swift">AccessUser row + Employee row = one Employee entity
AccessUser row + Visitor row = one Visitor entity
</code></pre>
<p>This produces a normalized schema and a clean Java hierarchy, but it also introduces joins and multi-table persistence operations.</p>
<p>Before choosing <code>JOINED</code>, draw three things:</p>
<ol>
<li><p><em>The Java class hierarchy.</em></p>
</li>
<li><p><em>The resulting database tables.</em></p>
</li>
<li><p><em>The SQL required to save and load a subtype.</em></p>
</li>
</ol>
<p>If those three views accurately represent the business domain and the query cost is acceptable <code>JOINED</code> can be a clear and production-ready inheritance strategy.</p>
<hr />
<h2><strong>References</strong></h2>
<ul>
<li><p><a href="https://jakarta.ee/specifications/platform/11/apidocs/jakarta/persistence/inheritancetype">Jakarta Persistence: <code>InheritanceType</code></a></p>
</li>
<li><p><a href="https://docs.jboss.org/hibernate/orm/current/userguide/html_single/Hibernate_User_Guide.html#entity-inheritance">Hibernate ORM User Guide: Inheritance</a></p>
</li>
</ul>
]]></content:encoded></item></channel></rss>