Embeddables & @Embedded
Embeddables & @Embedded
Not every object in your domain model needs its own database table. Sometimes a cluster of related fields — a postal address, a money amount, a date range — forms a coherent value object that belongs together conceptually, yet it makes perfect sense to store all its columns in the owning entity's table. JPA formalises this pattern through two annotations: @Embeddable and @Embedded.
The Problem: Flat Columns, Rich Domain
Consider a Customer entity that has a shipping address. A naive mapping spreads the address fields directly on the entity:
This compiles and works, but you lose the domain concept of an address. You cannot reuse the address structure on an Order entity, you cannot add address-specific validation in one place, and you cannot pass an Address object around your service layer. The database schema is identical either way — the goal is richer Java without changing the schema.
@Embeddable and @Embedded
Mark a plain class with @Embeddable to tell Hibernate it is a value object whose fields should be inlined into the owning table. Then annotate the field in the owning entity with @Embedded.
Hibernate maps this to a single customers table with columns id, name, street, city, postal_code, and country. No join, no extra table, no foreign key — just flat columns.
@Embedded explicitly is considered good practice because it communicates intent clearly to readers who may not remember that Address carries the annotation.
Overriding Column Names with @AttributeOverride
The default column names come from the field names in the @Embeddable class. Problems arise when you embed the same type twice in one entity — for example, a Customer that has both a shipping address and a billing address. Both would try to create a column named street, causing a mapping conflict.
Resolve the conflict with @AttributeOverride:
Now the table has eight distinct columns prefixed with ship_ and bill_, yet the Java code works with two cleanly typed Address objects.
Null Embeddables
When all columns belonging to an embedded value are NULL in the database, Hibernate returns null for the entire embedded field by default. This can produce a NullPointerException the first time you call customer.getShippingAddress().getCity(). Guard against it with a null check, or initialise the field to a sentinel instance in the entity constructor.
shippingAddress was never set before a record was saved, Hibernate will hand you null when you load it — not an empty Address object. Always initialise embedded fields to a safe default in your entity constructor, or check for null at the call site.
Nesting Embeddables
An @Embeddable class can itself contain another @Embeddable. A common real-world example is an Address that embeds a GeoPoint:
Hibernate flattens all nested columns into the same table. Keep nesting shallow — one or two levels is the practical limit before readability and column-override bookkeeping become painful.
Embeddables as Value Objects: Immutability and equals/hashCode
A value object should be immutable and compared by value, not identity. Make @Embeddable classes immutable where possible — provide only getters, set all fields in the constructor, and implement equals() and hashCode() based on the field values:
equals()/hashCode(), and a protected no-arg constructor for JPA. In Java 16+ you can use an actual record annotated with @Embeddable — Hibernate 6.2+ supports this natively, with the canonical constructor replacing the all-args constructor and JPA using the compact constructor for hydration.
Performance Trade-offs
Embedded values are stored in the same row as their owner, which means:
- No join required — reading a
Customeralways retrieves theAddressin the sameSELECT. There is no lazy-loading option and no N+1 risk for the embedded portion. - No partial loading — if you need only the customer name, the address columns are still fetched. Use projections (constructor expressions in JPQL or Spring Data Projections) when you genuinely need to avoid loading large embedded objects.
- Wide tables — embedding many value objects in one entity produces tables with many columns. This is usually fine; relational engines handle wide rows well. Consider normalisation only when the embedded data is genuinely optional and large.
Summary
Use @Embeddable and @Embedded to break a flat column set into rich, reusable value objects without changing your database schema. Apply @AttributeOverride whenever the same embeddable type appears more than once in an entity. Make embedded classes immutable and implement value-based equality. The trade-off is straightforward: you gain domain richness and reusability with zero extra joins, but you cannot lazily load an embedded object or share it across multiple rows. These properties make embeddables the right choice for address, money, date-range, and coordinate types that naturally belong with their owner.