What does @Embedded do in Spring Data JDBC, and what do its onEmpty and prefix attributes control?
answer
- Flatten value object into owner's columns
- onEmpty: USE_NULL vs USE_EMPTY (mandatory)
- @Embedded.Nullable / @Embedded.Empty shorthands
- prefix to embed same type twice
- No table, no join, no @Id
basics
~20 s@Embedded flattens a value object's properties into columns of the owner's table instead of a separate table. prefix adds a string before each embedded column name; onEmpty (USE_NULL or USE_EMPTY) decides whether an all-null embedded reads back as null or as an empty object.
solid answer
~40 s`@Embedded` (org.springframework.data.relational.core.mapping.Embedded) inlines a nested value object so its properties become additional columns in the **owning** entity's table — no join, no child table. You must specify `onEmpty`: `OnEmpty.USE_NULL` sets the embedded field to null when all its columns are null on read; `OnEmpty.USE_EMPTY` always instantiates the embedded object (empty). Shorthand `@Embedded.Nullable` and `@Embedded.Empty` cover those two cases. The `prefix` attribute prepends a fixed string to every embedded column name, which is essential when you embed the same value type twice (e.g. `home` and `work` addresses) to avoid column collisions. Column names are still produced by the NamingStrategy, then the prefix is applied. It's ideal for DDD value objects (Money, Address, Period) you want stored inline rather than as separate aggregates.
code
java · 17 linesimport org.springframework.data.relational.core.mapping.Embedded;
import static org.springframework.data.relational.core.mapping.Embedded.OnEmpty.USE_NULL;
import org.springframework.data.annotation.Id;
record Address(String street, String city, String zip) {}
class Customer {
@Id Long id;
String name;
// both flattened into the CUSTOMER table, disambiguated by prefix:
@Embedded(onEmpty = USE_NULL, prefix = "home_")
Address home; // -> home_street, home_city, home_zip
@Embedded.Empty(prefix = "work_") // shorthand for onEmpty = USE_EMPTY
Address work; // -> work_street, work_city, work_zip (never null)
}go deeper
Know @Embedded inlines a value object's fields into the owner's table.
Explain onEmpty (USE_NULL vs USE_EMPTY) and when prefix is needed for duplicate types.
Reason about the all-null ambiguity and how embedded interacts with NamingStrategy/@Column.
Decide value-object-inline vs separate aggregate, and how embedded fits DDD aggregate boundaries.
## Purpose `@Embedded` lets you model a **value object** — a small, identity-less bundle of fields like `Address`, `Money`, or `Period` — as a nested Java object while storing its fields as plain columns **in the same table** as the owning entity. There is no separate table and no join; it is pure column flattening. ## Mandatory onEmpty `@Embedded` requires an `onEmpty` value of type `OnEmpty` because the framework must decide what an all-null set of embedded columns means on read: - `OnEmpty.USE_NULL` → if **every** embedded column is null, the embedded property is set to `null`. - `OnEmpty.USE_EMPTY` → the embedded object is **always** instantiated (with null/default fields), never null. Two convenience meta-annotations exist so you don't write the enum: - `@Embedded.Nullable` = `@Embedded(onEmpty = USE_NULL)` - `@Embedded.Empty` = `@Embedded(onEmpty = USE_EMPTY)` Choosing `USE_EMPTY` is handy when the embedded type is a non-null field or you want to avoid null checks; `USE_NULL` preserves "absent" semantics. ## prefix — avoiding collisions `prefix` prepends a literal string to each embedded column name. Without it, embedding the **same** value type twice produces duplicate column names: ```java @Embedded(onEmpty = USE_NULL, prefix = "home_") Address home; @Embedded(onEmpty = USE_NULL, prefix = "work_") Address work; ``` If `Address` has `street` and `city`, you get columns `home_street, home_city, work_street, work_city`. The prefix is applied **on top of** whatever the NamingStrategy produced for each embedded property. ## Interaction with NamingStrategy and @Column Each embedded property's base column name still goes through the NamingStrategy (or a `@Column` on the embedded field). The prefix is then concatenated. So a global snake_case NamingStrategy plus `prefix = "home_"` yields `home_street`, `home_city`, etc. ## Nesting and identity - Embedded objects can themselves contain embedded objects (nested flattening). - An embedded object has **no identity** — it is not an aggregate root and has no `@Id`. It is loaded and saved as part of the owner. ## Gotchas - Forgetting `onEmpty` is a compile-time requirement (no default) — you must pick one or use the shorthand annotation. - All-null ambiguity: with `USE_NULL`, an `Address` where every field legitimately is null is indistinguishable from "no address"; if that matters, use `USE_EMPTY` or add a non-null discriminator column. - Prefix collisions: if two embeddeds share a prefix, columns still collide — prefixes must be distinct. - Embedded is **not** for cross-aggregate relationships; use `AggregateReference` for those.
- You embed the same Address type twice without a prefix. What happens?Both embeddeds resolve to the same column names (street, city, ...), causing a mapping/DDL collision. You must give at least one a distinct prefix so the flattened columns are unique.
saying these in an interview costs you the question
- Thinking @Embedded creates a separate table or a join
- Believing onEmpty is optional (it is required unless you use the shorthand annotation)
- Giving an embedded value object an @Id