Back to Embeddable, Inheritance & Secondary Tables
Q3Question 3 of 5

What is @Embeddable / @Embedded?

Key Concepts
  • @Embeddable marks a value object class that has no identity and no table of its own
  • @Embedded places an embeddable's fields as columns in the owning entity's table with no join
  • Value objects are defined by their values, not by an id -- think Money(amount + currency) or Address(street + city + pincode)
  • @AttributeOverride renames columns when the same embeddable type appears twice in one entity
  • Embeddables share the owner's table and are always loaded eagerly with the entity
  • Embeddables can be nested, and overrides reach nested paths using dotted names like balance.amount
  • A completely null embedded value means all columns are null -- distinguish no-address from address-with-null-fields

Definition

@Embeddable marks a value object class stored inline in the owning entity's table; @Embedded includes it in the entity; @AttributeOverride renames columns when the same embeddable type appears twice.

Three annotations work together to model value objects in JPA.

  • @Embeddable – marks a class as a value object whose fields are stored as columns in the owning entity's table, not in a separate table.
  • @Embedded – marks a field in an entity that holds an instance of an embeddable, merging its columns into the entity's table.
  • Value Object – an object defined by its values rather than by an identity, such as Money or Address.
  • @AttributeOverride – renames the columns of an embedded field to prevent column name clashes when the same embeddable type is used twice.
  • @AttributeOverrides – groups multiple @AttributeOverride annotations when several columns need renaming.

At National Bank of India, a customer's address is not a separate account -- it is a bundle of street, city, and pincode that only exists as part of the customer record. @Embeddable says "address is a reusable bundle" and @Embedded says "stamp that bundle's fields directly onto the customer's row." If Priya has both a home address and an office address, both bundles try to write to columns named street, city, and pincode, which collides. @AttributeOverride is the instruction that says "for the office bundle, write to office_street, office_city, and office_pincode instead" so both fit on one record.

Understanding the Concepts

What is @Embeddable?

@Embeddable marks a class as a value object in JPA. A value object has no identity column and no database table of its own. Its fields are stored as columns directly in the owning entity's table. Common examples include Money (amount + currency), Address (street + city + pincode), and DateRange (startDate + endDate).

At National Bank of India, Rajesh models the account balance as a Money value object that bundles the amount and currency together, ensuring they always travel as a pair.

JAVA
package com.bank.model;

import jakarta.persistence.Embeddable;
import jakarta.persistence.Column;
import java.math.BigDecimal;

@Embeddable
public class Money {

    @Column(precision = 19, scale = 4)
    private BigDecimal amount;

    @Column(length = 3)
    private String currency;

    protected Money() {} // JPA requires a no-arg constructor

    public Money(BigDecimal amount, String currency) {
        this.amount = amount;
        this.currency = currency;
    }

    public BigDecimal getAmount() { return amount; }
    public String getCurrency() { return currency; }
}

What is @Embedded?

@Embedded marks a field in an entity that holds an instance of an embeddable class. When JPA sees this annotation, it merges the embeddable's columns into the entity's table. There is no separate table and no join required.

JAVA
package com.bank.model;

import jakarta.persistence.*;
import java.math.BigDecimal;

@Entity
@Table(name = "accounts")
public class Account {

    @Id
    @Column(name = "account_id")
    private String accountId; // SAV2001

    @Column(name = "holder_name")
    private String holderName; // Priya

    @Embedded
    private Money balance;

    // getters and setters
}

The accounts table will have columns: account_id, holder_name, amount, and currency. The Money fields are stored inline, not in a separate table.

TEXT
accounts table:
account_id | holder_name | amount    | currency
SAV2001    | Priya       | 50000.00  | INR
SAV2002    | Rajesh      | 120000.00 | INR

Why do we need value objects in JPA?

Value objects solve a real problem in banking applications. Consider storing money without a value object.

JAVA
// Without value object -- amount and currency are loose fields
@Entity
public class Account {
    @Id private String accountId;
    private BigDecimal amount;
    private String currency;
}

This looks fine until a developer changes the amount without changing the currency, or copies the amount from one account to another without copying the currency. At National Bank of India, Rajesh found a bug where an INR amount was tagged as USD because the two fields drifted apart during a funds-transfer operation.

Bundling them into a Money value object solves this:

  • The domain model is cleaner because Money encapsulates its own invariants.
  • You cannot accidentally change one field without the other.
  • You can reuse the same Money type across Account, Transaction, and LoanApplication.
  • equals and hashCode are based on all fields, so two Money objects with the same amount and currency are equal.
JAVA
package com.bank.model;

import java.math.BigDecimal;
import java.util.Objects;

@Embeddable
public class Money {
    private BigDecimal amount;
    private String currency;

    protected Money() {}

    public Money(BigDecimal amount, String currency) {
        this.amount = amount;
        this.currency = currency;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Money m)) return false;
        return Objects.equals(amount, m.amount)
            && Objects.equals(currency, m.currency);
    }

    @Override
    public int hashCode() {
        return Objects.hash(amount, currency);
    }
}

How does @Embeddable work internally?

When Hibernate processes an @Embedded field, it reads the @Embeddable class metadata and adds each of its mapped fields as columns to the owning entity's table. There is no separate INSERT or SELECT for the embedded value. The embeddable's fields become part of the entity's column list.

DIAGRAM
Entity: Account (table = accounts)
  ├── accountId    → account_id (PK)
  ├── holderName   → holder_name
  └── @Embedded Money balance
        ├── amount   → amount
        └── currency → currency

Generated SQL:
  INSERT INTO accounts (account_id, holder_name, amount, currency)
  VALUES (?, ?, ?, ?)

The embeddable shares the entity's persistence context. It has no identity, no @Id, and no lifecycle of its own. When the entity is persisted, updated, or removed, the embeddable goes with it. Hibernate internally models the embeddable as a "component" in its metadata.

JAVA
package com.bank.service;

import com.bank.model.Account;
import com.bank.model.Money;
import jakarta.persistence.EntityManager;
import java.math.BigDecimal;

public class AccountService {

    private final EntityManager em;

    public AccountService(EntityManager em) { this.em = em; }

    public void createAccount() {
        Account acc = new Account();
        acc.setAccountId("SAV2001");
        acc.setHolderName("Priya");
        acc.setBalance(new Money(new BigDecimal("50000.00"), "INR"));

        em.persist(acc); // single INSERT with all columns
    }
}

How do you use @AttributeOverride when the same type appears twice?

When an entity embeds the same embeddable type twice, both instances map to the same column names by default. This causes a column name clash. @AttributeOverride renames the columns of one or both embedded fields.

At National Bank of India, Priya's loan application has both a disbursement amount and a repayment amount, both modeled as Money.

JAVA
package com.bank.model;

import jakarta.persistence.*;

@Entity
@Table(name = "loan_applications")
public class LoanApplication {

    @Id
    @Column(name = "loan_id")
    private String loanId;

    @Column(name = "applicant_name")
    private String applicantName; // Priya

    @Embedded
    private Money disbursement; // default columns: amount, currency

    @Embedded
    @AttributeOverrides({
        @AttributeOverride(name = "amount",
            column = @Column(name = "repay_amount", precision = 19, scale = 4)),
        @AttributeOverride(name = "currency",
            column = @Column(name = "repay_currency", length = 3))
    })
    private Money repayment;

    // getters and setters
}

The resulting table has columns: loan_id, applicant_name, amount, currency, repay_amount, repay_currency. Without @AttributeOverride, Hibernate throws a MappingException at startup.

TEXT
loan_applications table:
loan_id | applicant_name | amount    | currency | repay_amount | repay_currency
LN001   | Priya          | 500000.00 | INR      | 550000.00    | INR

How is @Embeddable different from @Entity?

This is a common interview comparison. The two annotations serve fundamentally different purposes.

| Feature | @Embeddable | @Entity |
|---|---|---|
| Identity | No @Id, no identity | Has @Id, has identity |
| Table | No own table, stored in owner's table | Has its own table |
| Lifecycle | Tied to owning entity | Independent lifecycle |
| Queries | Cannot query directly | Can query with JPQL or Criteria |
| Relationships | Cannot be target of @ManyToOne | Can be target of relationships |
| Equals/HashCode | Based on all field values | Based on @Id |
| Loading | Always eager (shares owner's table) | Can be lazy loaded |

JAVA
// @Embeddable -- value object, no identity
@Embeddable
public class Money {
    private BigDecimal amount;
    private String currency;
    // No @Id!
}

// @Entity -- has identity, has its own table
@Entity
public class Account {
    @Id
    private String accountId; // SAV2001
    private String holderName;
    @Embedded
    private Money balance;
}

Choose @Embeddable when the concept has no meaning outside its owner -- money, address, date range. Choose @Entity when it has an independent identity and lifecycle -- account, customer, transaction.

What are the rules and constraints?

Embeddable types have specific rules that you must follow.

  • An embeddable class must have a no-argument constructor (public or protected). JPA uses it to instantiate the object.
  • An embeddable class must not have an @Id field. It has no identity of its own.
  • An embeddable class cannot be the target of a relationship annotation like @ManyToOne.
  • Embeddable fields are always loaded eagerly. Because they share the owner's table, there is no way to lazy-load them.
  • If all columns of an embedded field are null in the database, JPA returns a null reference for the embedded field, not an object with null fields.
  • An embeddable can contain @Basic, @Column, @Temporal, @Enumerated, and even @ManyToOne relationships (with @AssociationOverride to customize the join column).
JAVA
package com.bank.model;

import jakarta.persistence.*;
import java.math.BigDecimal;

@Embeddable
public class Money {

    @Column(name = "amount", precision = 19, scale = 4)
    private BigDecimal amount;

    @Column(name = "currency", length = 3)
    private String currency;

    protected Money() {} // Required no-arg constructor

    public Money(BigDecimal amount, String currency) {
        this.amount = amount;
        this.currency = currency;
    }

    public BigDecimal getAmount() { return amount; }
    public String getCurrency() { return currency; }
}

What are common mistakes?

Mistake 1: Forgetting @AttributeOverride when embedding the same type twice.

JAVA
// WRONG -- both Money fields map to the same columns
@Entity
public class LoanApplication {
    @Id private String loanId;

    @Embedded
    private Money disbursement; // maps to: amount, currency

    @Embedded
    private Money repayment; // ALSO maps to: amount, currency
}
// Hibernate throws:
// org.hibernate.MappingException: Repeated column in mapping for entity
JAVA
// CORRECT -- use @AttributeOverride on the second field
@Embedded
@AttributeOverrides({
    @AttributeOverride(name = "amount",
        column = @Column(name = "repay_amount")),
    @AttributeOverride(name = "currency",
        column = @Column(name = "repay_currency"))
})
private Money repayment;

Mistake 2: Not handling null embedded values.

JAVA
// WRONG -- NullPointerException if balance columns are all null
Account account = em.find(Account.class, "SAV2001");
BigDecimal amt = account.getBalance().getAmount(); // NPE!
JAVA
// CORRECT -- check for null first
Money balance = account.getBalance();
if (balance != null) {
    BigDecimal amt = balance.getAmount();
}

Mistake 3: Missing the no-argument constructor.

JAVA
// WRONG -- JPA cannot instantiate this class
@Embeddable
public class Money {
    private BigDecimal amount;
    private String currency;

    public Money(BigDecimal amount, String currency) {
        this.amount = amount;
        this.currency = currency;
    }
    // No no-arg constructor!
    // JPA throws InstantiationException
}
JAVA
// CORRECT -- add a protected no-arg constructor
@Embeddable
public class Money {
    private BigDecimal amount;
    private String currency;

    protected Money() {} // JPA needs this

    public Money(BigDecimal amount, String currency) {
        this.amount = amount;
        this.currency = currency;
    }
}

What about nested embeddables?

An embeddable can contain another embeddable. At National Bank of India, Pankaj designs an AccountDetails embeddable that contains a Money balance and an Address branch address.

JAVA
package com.bank.model;

import jakarta.persistence.*;

@Embeddable
public class Address {
    private String street;
    private String city;
    private String pincode;
    protected Address() {}
}

@Embeddable
public class AccountDetails {
    @Embedded
    private Money balance;

    @Embedded
    private Address branchAddress;

    protected AccountDetails() {}
}

@Entity
@Table(name = "accounts")
public class Account {
    @Id
    private String accountId;

    @Embedded
    @AttributeOverrides({
        @AttributeOverride(name = "balance.amount",
            column = @Column(name = "bal_amount")),
        @AttributeOverride(name = "balance.currency",
            column = @Column(name = "bal_currency")),
        @AttributeOverride(name = "branchAddress.street",
            column = @Column(name = "branch_street")),
        @AttributeOverride(name = "branchAddress.city",
            column = @Column(name = "branch_city")),
        @AttributeOverride(name = "branchAddress.pincode",
            column = @Column(name = "branch_pin"))
    })
    private AccountDetails details;
}

Notice the dotted path syntax in @AttributeOverride: balance.amount reaches through the outer embeddable into the nested one. This works to any nesting depth.

What about collections of embeddables?

JPA supports collections of embeddable types using @ElementCollection. The collection is stored in a separate collection table, not inline in the entity's table.

JAVA
package com.bank.model;

import jakarta.persistence.*;
import java.util.List;

@Entity
@Table(name = "customers")
public class Customer {

    @Id
    @Column(name = "customer_id")
    private String customerId;

    private String name; // Priya

    @ElementCollection
    @CollectionTable(
        name = "customer_addresses",
        joinColumns = @JoinColumn(name = "customer_id"))
    private List<Address> addresses;
}

This creates a customer_addresses table with columns: customer_id (FK), street, city, pincode. Each address row references its owning customer. You can combine @ElementCollection with @AttributeOverride to rename the columns in the collection table.

TEXT
customers table:             customer_addresses table:
customer_id | name           customer_id | street         | city   | pincode
C001        | Priya          C001        | MG Road        | Mumbai | 400001
                             C001        | Brigade Road   | Delhi  | 110001

What should a senior engineer know?

  • Immutability. Value objects should be immutable. Once created, their fields should not change. Make fields final where possible and provide no setters. JPA can still populate them via reflection.
  • equals and hashCode. Because embeddables are value objects, their equals and hashCode must be based on all fields, not on an id. Two Money objects with the same amount and currency must be equal.
  • Hibernate component mapping. Under the hood, Hibernate models embeddables as "components" in its metadata. The component has no identifier and is always part of the owning entity's state.
  • @AssociationOverride. If an embeddable contains a @ManyToOne relationship, you can use @AssociationOverride to rename the join column when embedding the same type twice.
  • Performance. Embeddables add zero overhead at query time because they share the owner's table. There is no join. They are the most efficient way to model multi-field value objects.
  • Java records (Java 16+). Hibernate 6 supports @Embeddable on Java records, which gives you immutability and auto-generated equals/hashCode for free.
JAVA
package com.bank.model;

import jakarta.persistence.Embeddable;
import java.math.BigDecimal;

// Java 16+ -- embeddable as a record
@Embeddable
public record Money(BigDecimal amount, String currency) {}
JAVA
package com.bank.service;

import com.bank.model.Account;
import com.bank.model.Money;
import jakarta.persistence.EntityManager;
import java.math.BigDecimal;

public class AccountService {

    private final EntityManager em;

    public AccountService(EntityManager em) { this.em = em; }

    public void demo() {
        Money m1 = new Money(new BigDecimal("50000.00"), "INR");
        Money m2 = new Money(new BigDecimal("50000.00"), "INR");
        System.out.println("Equal: " + m1.equals(m2)); // true -- value equality

        Account acc = em.find(Account.class, "SAV2001");
        System.out.println("Balance: " + acc.getBalance());
    }
}
TEXT
Expected output:
Equal: true
Balance: Money[amount=50000.00, currency=INR]

Visual Overview

DIAGRAM
  @Embeddable Money          @Entity Account (table = accounts)
  ┌──────────────┐           ┌──────────────────────────────────────┐
  │ amount       │──embed──▶ │ account_id  (PK)                     │
  │ currency     │           │ holder_name                          │
  └──────────────┘           │ amount       ◀── from @Embedded Money│
  (no table, no @Id)         │ currency     ◀── from @Embedded Money│
                             └──────────────────────────────────────┘
DIAGRAM
  Same @Embeddable twice with @AttributeOverride:

  @Entity LoanApplication (table = loan_applications)
  ┌─────────────────────────────────────────────────┐
  │ loan_id           (PK)                          │
  │ applicant_name                                  │
  │ amount            ◀── @Embedded Money disbursement (default)    │
  │ currency          ◀── @Embedded Money disbursement (default)    │
  │ repay_amount      ◀── @Embedded Money repayment  (overridden)   │
  │ repay_currency    ◀── @Embedded Money repayment  (overridden)   │
  └─────────────────────────────────────────────────┘
DIAGRAM
  Nested embeddable path:

  Account
    └── @Embedded AccountDetails details
          ├── @Embedded Money balance
          │     ├── amount   → override path: "balance.amount"
          │     └── currency → override path: "balance.currency"
          └── @Embedded Address branchAddress
                ├── street   → override path: "branchAddress.street"
                ├── city     → override path: "branchAddress.city"
                └── pincode  → override path: "branchAddress.pincode"

Key Takeaway

An @Embeddable class is a value object in JPA. It has no identity, no table of its own, and no independent lifecycle. Its fields are stored as columns directly in the owning entity's table. When you mark a field with @Embedded, JPA merges the embeddable's columns into the entity's table with no join and no extra query. This is the cleanest way to model concepts like Money, Address, or DateRange that have no meaning outside their owner.

The interesting challenge arises when the same embeddable type appears twice in one entity. At National Bank of India, Rajesh's loan application has both a disbursement amount and a repayment amount, both modeled as Money. By default, both fields try to create columns named amount and currency, which clashes. The @AttributeOverride annotation renames one set of columns so both fit in the same table without conflict.

Value objects should be immutable with equals and hashCode based on all their fields. A null embedded value means all its columns are null in the database, so always check for null before accessing embedded fields. Embeddables can be nested, collected with @ElementCollection, and even contain relationships. They add zero query overhead because they share the owner's table, making them the most efficient way to model multi-field value objects in a banking domain where Money, Address, and DateRange appear everywhere.

Remember one sentence: @Embeddable is a value object stored inline in the owner's table, @Embedded includes it, and @AttributeOverride renames its columns when the same type appears twice.

---

Share
Use ← → arrow keys to navigate