# Understanding JPA JOINED Inheritance: From Java Subtypes to Database Tables


![](https://cdn.hashnode.com/uploads/covers/6358a115ff3bb3047322d0b5/48f5cda1-7258-43f0-ba90-372e387eca12.png align="center")

## **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 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.

> <mark class="bg-yellow-200 dark:bg-yellow-500/30">Java represents this naturally with inheritance:</mark>

```plaintext
AccessUser
├── Employee
└── Visitor
```

> *<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>*

**This creates an important question:**

> *<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>*

JPA’s `JOINED` inheritance strategy answers this.

This note focuses exclusively on `InheritanceType.JOINED`. JPA also supports `SINGLE_TABLE` and `TABLE_PER_CLASS`, while `@MappedSuperclass` is commonly discussed as another inheritance-mapping approach. We will leave those alternatives for separate articles.

* * *

Consider a simplified access-control system.

Every person who receives access credentials is represented as an `AccessUser`. **Employees** and **Visitors** are specialized types of access users.

The parent class contains information shared by every subtype:

```java
public abstract class AccessUser {

    protected Long id;
    protected String firstName;
    protected String lastName;
    protected String credential;
}
```

An employee adds employee-specific information:

```java
public class Employee extends AccessUser {

    private String employeeId;
    private String email;
}
```

A visitor adds visitor-specific information:

```java
public class Visitor extends AccessUser {

    private String visitorId;
    private String company;
}
```

The entity hierarchy looks like this:

This hierarchy represents a genuine subtype relationship:

![](https://cdn.hashnode.com/uploads/covers/6358a115ff3bb3047322d0b5/cd04f1e4-6be8-42ae-9001-a1e54452faec.png align="center")

```sql
An Employee is an AccessUser.
A Visitor is an AccessUser.
```

The next challenge is mapping that object model to relational tables.

* * *

## **Mapping the Hierarchy with JOINED**

The inheritance strategy is declared on the root entity:

```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() {
    }
}
```

> The key annotation is:

```javascript
@Inheritance(strategy = InheritanceType.JOINED)
```

It tells the JPA provider to:

1.  *Use one table for the root entity.*
    
2.  *Use a separate table for each managed subtype.*
    
3.  *Store common fields in the root table.*
    
4.  *Store subtype-specific fields in the subtype table.*
    
5.  *Connect the parent and subtype rows using the same primary key.*
    

Declare the strategy only on the root of the hierarchy. It does not need to be repeated on every subtype.

The concrete subtypes still need to be JPA entities:

```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() {
    }
}
```

```java
@Entity
@Table(name = "visitors")
public class Visitor extends AccessUser {

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

    private String company;

    protected Visitor() {
    }
}
```

Each subtype uses `extends AccessUser` for Java inheritance and `@Entity` for JPA persistence.

* * *

## **The Resulting Database Tables**

The mapping produces one parent table and one table for each subtype:

![](https://cdn.hashnode.com/uploads/covers/6358a115ff3bb3047322d0b5/7c1c92cd-ef56-4b3a-a6d4-475657aa78ce.png align="center")

A simplified PostgreSQL schema could look like this:

```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)
);
```

The most important part of this structure is the subtype ID:

```plaintext
employees.id → access_users.id
visitors.id  → access_users.id
```

The subtype table’s `id` is both:

*   its primary key;
    
*   a foreign key to `access_user.id`
    

Suppose both tables contain a row with ID `42`:

```javascript
access_users
────────────────────────────────────────────
id    first_name    last_name    credential
42    Ethan         Zay          CARD-1007

visitors
────────────────────────────────────
id    visitor_id    company
42    V99881        Example Ltd
```

These are not two independent business objects. Together, they represent one `Visitor`:

```sql
access_users row 42
        +
visitors row 42
        ↓
one Visitor entity
```

This is the central idea behind `JOINED` inheritance.

* * *

## **What Happens When Hibernate Saves a Visitor?**

Consider the following object:

```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);
```

Hibernate needs to persist both the inherited state and the visitor-specific state.

It first inserts the common fields into `access_users`:

```sql
INSERT INTO access_users (
    first_name,
    last_name,
    credential
)
VALUES (
    'Ethan',
    'Zay',
    'CARD-1007'
)
RETURNING id;
```

Assume that the database generates ID `42`. Hibernate then uses that ID when inserting the subtype fields:

```sql
INSERT INTO visitors (
    id,
    visitor_id,
    company
)
VALUES (
    42,
    'V99881',
    'Example Ltd'
);
```

One Java object therefore produces two database rows:

```sql
Visitor object
    │
    ├── common state ────────> access_users row 42
    │
    └── visitor state ───────> visitors row 42
```

Both inserts normally execute within the same transaction. If one insert fails, the transaction should roll back instead of leaving an incomplete entity behind.

## **What Happens When Hibernate Loads a Visitor?**

When the application loads visitor `42`, Hibernate needs information from both tables.

The exact generated SQL depends on the Hibernate version and database dialect, but its logical form is:

```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;
```

Hibernate combines the columns into one Java object:

```json
Visitor {
    id = 42,
    firstName = "Ethan",
    lastName = "Zay",
    credential = "CARD-1007",
    visitorId = "V99881",
    company = "Example Ltd"
}
```

The application works with one object even though its state is stored across two tables.

This join is both the mechanism and the principal cost of `JOINED` inheritance. Every subtype read requires Hibernate to join the subtype table with its parent table.

Because the root is also an entity, a query against `AccessUser` can return both `Employee` and `Visitor` instances. Hibernate may need additional joins to determine the concrete subtype, so parent-level queries become more expensive as a hierarchy grows.

* * *

## **Benefits of JOINED Inheritance**

The `JOINED` strategy provides several useful properties.

### **Normalized tables**

Common fields are stored once in the parent table instead of being repeated in every subtype table.

### **Strong subtype constraints**

Subtype-specific columns can have appropriate database constraints:

```sql
visitor_id     TEXT NOT NULL
employee_id    TEXT NOT NULL UNIQUE
```

This is harder to enforce when unrelated subtype columns share one large table.

### **Shared identity**

Every employee and visitor has a common `AccessUser` identity. Other parts of the system can reference that shared identity when the domain requires it.

### **Polymorphic entity model**

Application code can operate on the parent type when it does not need subtype-specific behavior.

The Jakarta Persistence specification describes `JOINED` as a well-normalized strategy with support for polymorphic relationships.

## **Trade-Offs to Consider**

`JOINED` is not automatically the best strategy for every hierarchy.

### Additional joins

Loading a subtype requires at least one join with its parent table.

### Queries across the hierarchy

Queries against the parent entity may need to inspect several subtype tables. A hierarchy with many subtypes can generate large SQL statements.

### Multi-table writes

Creating or deleting a subtype affects more than one table. Updating fields can also target different tables:

```sql
Changing firstName → UPDATE access_users
Changing company   → UPDATE visitors
```

### More complex schema changes

The database must maintain parent-child foreign-key integrity. Migrations and manual data operations need to respect that relationship.

The Java model may look simple, but developers should still inspect the SQL generated for important production queries.

## **Common implementation mistakes**

### Missing `@Entity` on a subtype

This is incomplete:

```java
@Table(name = "visitors")
public class Visitor extends AccessUser {
}
```

`@Table` specifies a table mapping, but it does not register the class as an entity.

The correct mapping is:

```java
@Entity
@Table(name = "visitors")
public class Visitor extends AccessUser {
}
```

### Declaring a Separate ID in the subtype

The subtype should inherit the parent ID. It should not introduce an unrelated primary key:

```java
@Entity
public class Visitor extends AccessUser {

    // Do not declare another @Id here.
}
```

The shared ID is what connects the parent and subtype rows.

### Using inheritance only to reuse fields

Two entities sharing `createdAt` and `updatedAt` does not necessarily mean they belong to the same entity hierarchy.

Use inheritance when the subtype truly represents the parent concept:

```sql
Visitor is an AccessUser → valid subtype
```

* * *

## **When JOINED is a good fit?**

**Consider** `JOINED` **when:**

*   the classes have a genuine “is-a” relationship;
    
*   the subtypes share meaningful business identity;
    
*   normalized tables are important;
    
*   subtype-specific constraints matter;
    
*   polymorphic behavior is required;
    
*   the hierarchy contains a controlled number of subtypes;
    
*   the cost of joins is acceptable.
    

**Reconsider it when:**

*   the classes only share technical fields;
    
*   the hierarchy contains many levels or subtypes;
    
*   most queries process the entire hierarchy at high volume;
    
*   query latency is more important than normalization;
    
*   the database structure would be simpler without entity inheritance.
    

## **Key takeaway**

JPA `JOINED` inheritance maps one Java entity across a parent table and a subtype table.

The parent table stores shared state. The subtype table stores specialized state. The same primary key connects both rows:

```swift
AccessUser row + Employee row = one Employee entity
AccessUser row + Visitor row = one Visitor entity
```

This produces a normalized schema and a clean Java hierarchy, but it also introduces joins and multi-table persistence operations.

Before choosing `JOINED`, draw three things:

1.  *The Java class hierarchy.*
    
2.  *The resulting database tables.*
    
3.  *The SQL required to save and load a subtype.*
    

If those three views accurately represent the business domain and the query cost is acceptable `JOINED` can be a clear and production-ready inheritance strategy.

* * *

## **References**

*   [Jakarta Persistence: `InheritanceType`](https://jakarta.ee/specifications/platform/11/apidocs/jakarta/persistence/inheritancetype)
    
*   [Hibernate ORM User Guide: Inheritance](https://docs.jboss.org/hibernate/orm/current/userguide/html_single/Hibernate_User_Guide.html#entity-inheritance)
