In JPA/Hibernate, what does annotating a class @Embeddable and referencing it with @Embedded from another class actually do, and how does such a value type differ from a class annotated @Entity?
answer
- Component = value type, no id
- Columns inlined into owner's table
- No persist/remove, no cascade
- Never share one instance between entities
- All-null columns read back as null
basics
~20 s@Embeddable marks a value type with no identity. Its fields become extra columns in the owner's table: no separate table, no primary key, no lifecycle of its own. An @Entity has an identifier, its own row, and is tracked individually by the persistence context.
solid answer
~50 sAn `@Embeddable` is a **value type** (Hibernate calls it a *component*). It has no identifier and no independent existence: when an entity declares `@Embedded Address address`, Hibernate inlines the embeddable's properties as **extra columns in the entity's own table**. No join, no second row, no `find()` by id, nothing to cascade. The value is loaded, dirty-checked and written as part of its owner. An `@Entity` has a primary key, its own row, its own lifecycle (`persist`/`remove`), and exactly one instance per id inside a persistence context. Embeddables exist to give a name and a type to a group of related columns (`Address`, `Money`, `Period`) and to reuse that grouping across entities. Requirements: a no-arg constructor and non-final fields (Hibernate 6 also supports Java records as embeddables). Because they are values, do **not** share one instance across two entities — copy it, or mutating it writes to both rows.
code
java · 13 lines@Embeddable
public class Money {
@Column(name = "amount") private BigDecimal amount;
@Column(name = "currency") private String currency;
protected Money() {}
public Money(BigDecimal amount, String currency) { this.amount = amount; this.currency = currency; }
}
@Entity
public class Invoice {
@Id @GeneratedValue private Long id;
@Embedded private Money total;
}go deeper
Recall the core contrast: value type versus entity, columns inlined into the owner's table, no id and no lifecycle. Show a two-field Address example.
Add the mechanics: dirty checking through the owner's snapshot, query paths without a join, what an embeddable may contain, and the all-null-reads-as-null asymmetry.
Argue for immutable value objects, explain why shared instances are unsafe, and connect embeddables to @EmbeddedId and @ElementCollection where equals/hashCode start to matter.
Frame it as domain modelling: which concepts deserve a type without deserving a table, how value objects keep invariants (amount+currency) together, and the schema-stability cost of promoting an embeddable to an entity later.
## Entities versus value types JPA splits persistent classes into two families. An **entity** has identity: a primary key, its own table row, and a guarantee of one instance per id inside a persistence context. A **value type** has no identity — it is state that belongs entirely to whoever holds it. `@Embeddable` declares a value type; `@Embedded` uses one inside an entity. ```java @Embeddable public class Address { private String street; private String city; private String zip; protected Address() {} } @Entity public class Customer { @Id @GeneratedValue private Long id; private String name; @Embedded private Address address; } ``` This produces **one table**: `customer(id, name, street, city, zip)`. There is no `address` table, no foreign key, no join at read time. `@Embedded` on the field is optional in practice — Hibernate recognises a type annotated `@Embeddable` — but writing it is clearer. ## Consequences of having no identity 1. **No lifecycle operations.** You never `persist()` or `remove()` an `Address`. It is written when the `Customer` is inserted and deleted when the row is deleted. Cascade settings are meaningless for it. 2. **Dirty checking is per column.** Hibernate snapshots the embeddable's columns as part of the owner's state, so `customer.getAddress().setCity("Berlin")` marks the owner dirty and issues an `UPDATE customer SET city=?`. This works only if the embeddable is genuinely mutable and reachable — replacing the whole value (`setAddress(new Address(...))`) is equally fine and usually cleaner. 3. **No shared references.** Two entities must never hold the same `Address` instance. Hibernate has no way to represent sharing (there is no row to point at), so a mutation would be flushed into both owners' rows. Value types should ideally be immutable: final-ish fields, no setters, replace instead of mutate. 4. **Query paths navigate through the attribute** without a join: `select c from Customer c where c.address.city = :city` compiles to a plain predicate on `customer.city`. ## What an embeddable may contain Basic attributes, other embeddables (nesting is allowed to any depth), `@ManyToOne`/`@OneToOne` associations, and even `@ElementCollection`. An embeddable can therefore carry a foreign key column into its owner's table. It can also serve as a composite identifier via `@EmbeddedId`, and as the element type of `@ElementCollection Set<Address>` — both of those uses require a correct `equals`/`hashCode`, whereas a plain `@Embedded` field does not strictly need one. ## Null semantics — the classic gotcha There is no null marker column for an embedded value. When Hibernate reads a row whose embeddable columns are all `NULL`, it hands you `null` for the attribute rather than an `Address` with null fields. So writing a non-null `Address` whose fields happen to be all null and reading it back gives `null` — the round trip is asymmetric. Code defensively, or make at least one column non-nullable. ## Why bother Embeddables buy domain expressiveness at zero schema cost: `Money(amount, currency)` used in five entities keeps the two columns paired and the arithmetic in one class, and `@AttributeOverride` lets each owner rename the columns. They are the JPA answer to "I want a type, not a table".
- Can an @Embeddable contain a @ManyToOne association, and where does the foreign key column live?Yes. An embeddable may declare `@ManyToOne` or `@OneToOne` associations, and the join column is added to the owning entity's table just like any basic column. The owner can rename it with `@AssociationOverride(name = "...", joinColumns = @JoinColumn(name = "..."))`. Embeddables may also declare `@ElementCollection`, which then maps to its own collection table keyed by the owner's id.
- Does an @Embeddable need equals() and hashCode()?Not for a plain `@Embedded` field — Hibernate compares the underlying columns for dirty checking, not the object. It becomes mandatory when the embeddable is used as an `@EmbeddedId` (Hibernate keys the persistence context and the second-level cache by identifier equality) or as the element type of a `Set` in an `@ElementCollection`, where set semantics depend on it.
An entity is a person with a passport number; an embeddable is their home address written on the passport page. The address has no independent existence — copy the passport and you copy the address.
saying these in an interview costs you the question
- Saying an @Embeddable gets its own table or requires a join
- Sharing one embeddable instance across two entities and expecting independent rows
- Calling persist() or remove() on an embeddable, or configuring cascade for it
- Assuming an all-null embeddable is read back as a non-null object with null fields
- Claiming an embeddable must have a primary key