Hibernate @EmbeddedTable Explained
Hibernate has traditionally supported embedded objects through the @Embeddable and @Embedded annotations, allowing developers to model reusable value objects without creating separate entity tables. However, storing embedded objects in a different database table required complex mappings using @SecondaryTable along with multiple @AttributeOverride and @AssociationOverride annotations. Hibernate ORM 7.2 introduces the new @EmbeddedTable annotation to simplify this use case by allowing developers to directly define the table where an embedded value object should be persisted. This Hibernate-specific feature reduces mapping complexity and provides a cleaner alternative to managing multiple override configurations when storing embedded values in secondary tables.
1. Overview
In object-oriented applications, many concepts do not require their own identity but still represent a meaningful collection of related attributes. Common examples include address information, contact details, personal information, payment details, and employee profile information. These objects are known as @value objects and are represented in Hibernate using the @Embeddable annotation. By default, Hibernate stores embedded fields in the same table as the owning entity. However, enterprise applications often require these value objects to be stored separately for better data organization, while keeping the Java domain model clean, reusable, and aligned with domain-driven design principles.
2. Understanding @EmbeddedTable Annotation in Hibernate
2.1 What is @EmbeddedTable?
@EmbeddedTable is a Hibernate ORM 7.2 annotation that allows developers to specify the database table where an embedded value object should be persisted. It simplifies the mapping of embedded objects to secondary tables by providing a direct table-level configuration instead of defining individual column overrides. It is commonly used with @Embeddable to define the value object, @Embedded to include the value object in an entity, and @SecondaryTable to define the additional table used for storing the embedded fields. This keeps the entity mapping cleaner by allowing Hibernate to apply the table mapping at the embedded object level.
@Entity
@Table(name="employee")
@SecondaryTable(name="employee_details")
public class Employee {
@Embedded
@EmbeddedTable("employee_details")
private Address address;
}
The above configuration tells Hibernate to persist the fields of the Address value object in the employee_details secondary table instead of the primary employee table. Without @EmbeddedTable, each attribute inside the embedded object would require separate column-level table mappings.
2.1.1 Advantages
- Cleaner entity mapping: Allows developers to define the target table directly on the embedded attribute, reducing mapping complexity.
- Avoids multiple AttributeOverride declarations: Eliminates repetitive column-level table overrides for every field in the embedded object.
- Improves readability: Keeps entity classes concise and makes the persistence mapping easier to understand.
- Supports better domain-driven design: Allows value objects to remain focused on business concepts without exposing unnecessary database mapping details.
- Keeps embedded objects reusable: The same embeddable object can be reused across different entities while allowing flexible storage configuration.
2.1.2 Limitations
- Hibernate specific:
@EmbeddedTableis a Hibernate feature and is not part of the Jakarta Persistence specification. - Hibernate version dependency: The annotation is available only in Hibernate ORM 7.2 and later versions.
- Secondary table requirement: The target table must be defined using
@SecondaryTablebefore it can be referenced by@EmbeddedTable. - Top-level embedded only: It is designed for embedded attributes directly declared on an entity or mapped superclass and does not apply to deeply nested embedded mappings.
2.2 Problem with Traditional Mapping
Before Hibernate 7.2, mapping an embedded object to a secondary table required developers to explicitly define the target table for each attribute using @AttributeOverride. While this approach worked, it introduced additional configuration overhead, especially for value objects containing many fields. Every new attribute added to the embedded object required an additional override definition, making entity mappings longer and harder to maintain. The persistence configuration also became tightly coupled with the database table structure.
@Entity
@Table(name="employee")
@SecondaryTable(name="employee_details")
public class Employee {
@Embedded
@AttributeOverride(
name="street",
column=@Column(table="employee_details")
)
@AttributeOverride(
name="city",
column=@Column(table="employee_details")
)
@AttributeOverride(
name="state",
column=@Column(table="employee_details")
)
private Address address;
}
In this example, each field from the Address value object must be individually mapped to the employee_details secondary table. As the embedded object grows, the number of annotations increases, reducing readability and making future changes more error-prone.
2.2.1 Limitations of Traditional Approach
The traditional @AttributeOverride-based approach works for simple mappings but becomes difficult to manage as embedded objects become larger or more complex. The mapping responsibility shifts from the value object level to individual fields, increasing maintenance effort.
- Repetitive configuration: Each embedded field requires a separate column override, resulting in duplicate mapping definitions.
- Higher maintenance effort: Adding or removing fields from the embedded object requires updating multiple mapping annotations.
- Tight database coupling: Entity classes become more dependent on the underlying table structure and column organization.
- Reduced readability: Large embedded objects make entity mappings lengthy and harder to understand.
3. Code Example
3.1 Address Embeddable Object
The Address class represents a reusable value object that contains related address attributes. It is marked with @Embeddable, allowing Hibernate to treat its fields as part of the owning entity mapping.
package com.example.demo;
import jakarta.persistence.Embeddable;
@Embeddable
public class Address {
private String street;
private String city;
private String state;
private String zipCode;
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public String getZipCode() {
return zipCode;
}
public void setZipCode(String zipCode) {
this.zipCode = zipCode;
}
}
The Address class does not have its own identity or database table. Instead, its attributes can be embedded into an entity and persisted according to the entity mapping configuration.
3.2 Employee Entity
The Employee entity demonstrates how @EmbeddedTable is used to store the embedded Address object in a separate secondary table. The @SecondaryTable annotation defines the additional table, while @EmbeddedTable associates the embedded fields with that table.
package com.example.demo;
import jakarta.persistence.*;
import org.hibernate.annotations.EmbeddedTable;
@Entity
@Table(name="employee")
@SecondaryTable(
name="employee_details"
)
public class Employee {
@Id
@GeneratedValue(
strategy = GenerationType.IDENTITY
)
private Long id;
private String name;
@Embedded
@EmbeddedTable("employee_details")
private Address address;
public Long getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
}
In this mapping, the employee-specific attributes are stored in the employee table, while the fields from the embedded Address object are persisted in the employee_details secondary table. This avoids the need for multiple @AttributeOverride definitions for each address field.
3.3 Main Class
The main class is used to bootstrap the application, create an employee object with an embedded address, and persist the entity using Hibernate.
package com.example.demo;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application implements CommandLineRunner {
private final EmployeeRepository employeeRepository;
public Application(EmployeeRepository employeeRepository) {
this.employeeRepository = employeeRepository;
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Override
public void run(String... args) {
Address address = new Address();
address.setStreet("MG Road");
address.setCity("Bangalore");
address.setState("Karnataka");
address.setZipCode("560001");
Employee employee = new Employee();
employee.setName("John Smith");
employee.setAddress(address);
employeeRepository.save(employee);
}
}
The application creates an Employee instance with an embedded Address value object and saves it through the repository. Hibernate uses the @EmbeddedTable mapping to store the address attributes in the configured secondary table.
3.4 Code Run and Output
The following output demonstrates the SQL statements generated by Hibernate while storing the entity and its embedded value object in separate tables.
Hibernate:
create table employee (
id bigint generated by default as identity,
name varchar(255),
primary key (id)
)
Hibernate:
create table employee_details (
employee_id bigint not null,
street varchar(255),
city varchar(255),
state varchar(255),
zip_code varchar(255)
)
Hibernate:
insert into employee
(name,id)
values
(?,default)
Hibernate:
insert into employee_details
(city, state, street, zip_code, employee_id)
values
(?, ?, ?, ?, ?)
The generated SQL shows that Hibernate stores the entity attributes in the employee table and persists the embedded Address fields in the employee_details secondary table based on the @EmbeddedTable configuration.
4. Conclusion
Hibernate 7.2 introduces @EmbeddedTable as a cleaner and more maintainable approach for mapping embedded value objects to secondary tables. Before this feature, developers had to define multiple @AttributeOverride annotations for each embedded field, resulting in verbose configurations that became difficult to manage as the object model grew. With @EmbeddedTable, the target table can be specified directly at the embedded attribute level, simplifying entity mappings and reducing ORM configuration complexity. For modern Hibernate applications using ORM 7.2, @EmbeddedTable provides a streamlined solution for handling complex embedded object persistence scenarios.

