Working with Embedded Types in JPA – @Embeddable and @Embedded Explained

Illustration for Working with Embedded Types in JPA – @Embeddable and @Embedded Explained
By Last updated:

In object-oriented programming, we often model entities with value objects that don’t have their own identity. For example, a User may have an Address containing fields like street, city, and zipcode. In relational databases, these fields typically live inside the same table as the entity, not in a separate one.

In JPA (Java Persistence API), such cases are handled using embedded types with @Embeddable and @Embedded. This allows you to reuse structured components across multiple entities without creating separate tables.

In this tutorial, we’ll explore how to use embedded types in JPA, complete with setup, code examples, SQL outputs, pitfalls, and best practices.


Core Definition of Embedded Types

  • @Embeddable: Declares a class as embeddable (value object).
  • @Embedded: Specifies that an embeddable type is a field inside an entity.
  • Embedded fields are stored as columns of the owning entity’s table.
  • They don’t have their own identity or lifecycle.

Setup and Configuration

persistence.xml

<persistence xmlns="https://jakarta.ee/xml/ns/persistence" version="3.0">
    <persistence-unit name="my-pu" transaction-type="RESOURCE_LOCAL">
        <class>com.example.User</class>
        <class>com.example.Address</class>
        <properties>
            <property name="jakarta.persistence.jdbc.driver" value="org.h2.Driver"/>
            <property name="jakarta.persistence.jdbc.url" value="jdbc:h2:mem:testdb"/>
            <property name="jakarta.persistence.jdbc.user" value="sa"/>
            <property name="jakarta.persistence.jdbc.password" value=""/>
            <property name="hibernate.hbm2ddl.auto" value="update"/>
            <property name="hibernate.show_sql" value="true"/>
        </properties>
    </persistence-unit>
</persistence>

Entity Example with Embedded Types

Address (Embeddable)

import jakarta.persistence.*;

@Embeddable
public class Address {
    private String street;
    private String city;
    private String zipcode;

    // Getters and setters
}

User (Entity with Embedded Address)

import jakarta.persistence.*;

@Entity
@Table(name = "users")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @Embedded
    private Address address;

    // Getters and setters
}

Generated Schema

create table users (
    id bigint generated by default as identity,
    name varchar(255),
    street varchar(255),
    city varchar(255),
    zipcode varchar(255),
    primary key (id)
);

CRUD Operations with Embedded Types

Create (Insert)

EntityManagerFactory emf = Persistence.createEntityManagerFactory("my-pu");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();

tx.begin();
Address address = new Address();
address.setStreet("123 Main St");
address.setCity("Lucknow");
address.setZipcode("226010");

User user = new User();
user.setName("Ashwani");
user.setAddress(address);

em.persist(user);
tx.commit();

SQL Output:

insert into users (name, street, city, zipcode) values ('Ashwani', '123 Main St', 'Lucknow', '226010');

Read (Fetch)

User found = em.find(User.class, 1L);
System.out.println(found.getName() + " lives in " + found.getAddress().getCity());

Update

tx.begin();
found.getAddress().setCity("Bangalore");
em.merge(found);
tx.commit();

SQL:

update users set city='Bangalore' where id=?;

Delete

tx.begin();
em.remove(found);
tx.commit();

SQL:

delete from users where id=?;

Persistence Context Considerations

  • Embedded objects are treated as part of the parent entity.
  • They don’t have their own persistence identity.
  • Updating the embedded object marks the parent entity as dirty and triggers an update.

Querying with JPA

JPQL

List<User> results = em.createQuery("SELECT u FROM User u WHERE u.address.city = :city", User.class)
                       .setParameter("city", "Lucknow")
                       .getResultList();

Criteria API

CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<User> cq = cb.createQuery(User.class);
Root<User> root = cq.from(User.class);
cq.select(root).where(cb.equal(root.get("address").get("city"), "Lucknow"));
List<User> users = em.createQuery(cq).getResultList();

Native SQL

List<Object[]> results = em.createNativeQuery("SELECT name, city FROM users WHERE city='Lucknow'").getResultList();

Real-World Use Cases

  • User ↔ Address
  • Order ↔ ShippingDetails
  • Employee ↔ ContactInfo
  • Audit fields (createdBy, createdDate) reusable across entities

Anti-Patterns and Pitfalls

  • Avoid embedding large objects with too many fields → bloated table.
  • Changing embedded object affects parent entity lifecycle.
  • No shared identity → don’t treat embeddables like full entities.

Best Practices

  • Use embedded types for value objects (address, contact info, audit fields).
  • Keep them small and reusable.
  • Avoid embedding collections; prefer entity relationships.
  • Use @AttributeOverrides to customize column mapping.

Example with @AttributeOverride

@Embedded
@AttributeOverrides({
    @AttributeOverride(name = "street", column = @Column(name = "home_street")),
    @AttributeOverride(name = "city", column = @Column(name = "home_city"))
})
private Address homeAddress;

📌 JPA Version Notes

  • JPA 2.0: Introduced Criteria API, Metamodel.
  • JPA 2.1: Added entity graphs, stored procedures.
  • Jakarta Persistence: Package renamed from javax.persistencejakarta.persistence.

Conclusion and Key Takeaways

  • Embedded types allow value objects to be reused across entities.
  • @Embeddable defines the class; @Embedded uses it inside entities.
  • They don’t have identity and live in the same table as the parent.
  • Ideal for addresses, audit fields, and other reusable components.

By mastering embedded types, you’ll design cleaner, modular, and production-ready JPA models.


FAQ

1. What’s the difference between JPA and Hibernate?
JPA is a specification; Hibernate is an implementation.

2. How does JPA handle the persistence context?
It manages entities, and embedded objects are tracked as part of their parent.

3. What are the drawbacks of eager fetching in JPA?
It loads unnecessary data, which can hurt performance.

4. How can I solve the N+1 select problem with JPA?
Use JOIN FETCH, batch fetching, or entity graphs.

5. Can I use JPA without Hibernate?
Yes, EclipseLink and OpenJPA are alternatives.

6. What’s the best strategy for inheritance mapping in JPA?
Depends: JOINED for normalization, SINGLE_TABLE for performance.

7. How does JPA handle composite keys?
With @EmbeddedId or @IdClass.

8. What changes with Jakarta Persistence?
Namespace changed from javax.persistence to jakarta.persistence.

9. Is JPA suitable for microservices?
Yes, but DTOs and projections help reduce payload size.

10. When should I avoid using JPA?
In large-scale batch processing or NoSQL-driven architectures.