Embeddable Types in Hibernate: @Embeddable and @Embedded Explained

Illustration for Embeddable Types in Hibernate: @Embeddable and @Embedded Explained
By Last updated:

When working with relational databases, we often encounter situations where multiple columns logically represent a single concept. For example, an address contains street, city, state, and zipCode. In object-oriented programming, it makes sense to encapsulate these values into a single class. Hibernate provides a clean way to achieve this using Embeddable types with @Embeddable and @Embedded annotations.

Embeddable types allow developers to build modular, reusable domain objects while maintaining normalized database schemas. This not only improves code readability and database design, but also makes applications more maintainable.

In this tutorial, we’ll dive deep into @Embeddable and @Embedded, explain how they work, provide Java + SQL examples, highlight best practices, and explore real-world use cases with Spring Boot integration.


Core Concept: What are Embeddable Types?

  • Embeddable types are reusable value objects (not entities) that represent a group of fields.
  • They do not have their own identity (primary key).
  • Instead, they are embedded into an entity using the @Embedded annotation.
  • Their fields are mapped to the same table as the owning entity.

Example

If a User entity has an Address object, the Address fields (like street, city, state) will be stored in the users table.


Hibernate Annotations for Embeddable Types

  1. @Embeddable
    Used on a class to mark it as an embeddable type.

  2. @Embedded
    Used in the entity to embed the embeddable class.

  3. @AttributeOverrides / @AttributeOverride
    Used when we need to override column names for fields inside an embeddable.


Example Setup

Database Table: users

CREATE TABLE users (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(100),
    street VARCHAR(150),
    city VARCHAR(100),
    state VARCHAR(50),
    zipcode VARCHAR(20)
);

Embeddable Class: Address

import jakarta.persistence.Embeddable;

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

    // getters and setters
}

Entity Class: User

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
}

Using Attribute Overrides

Sometimes, an entity may need multiple embedded objects of the same type. In such cases, we can override column mappings using @AttributeOverrides.

@Entity
@Table(name = "companies")
public class Company {

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

    private String name;

    @Embedded
    @AttributeOverrides({
        @AttributeOverride(name = "street", column = @Column(name = "billing_street")),
        @AttributeOverride(name = "city", column = @Column(name = "billing_city")),
        @AttributeOverride(name = "state", column = @Column(name = "billing_state")),
        @AttributeOverride(name = "zipcode", column = @Column(name = "billing_zipcode"))
    })
    private Address billingAddress;

    @Embedded
    @AttributeOverrides({
        @AttributeOverride(name = "street", column = @Column(name = "shipping_street")),
        @AttributeOverride(name = "city", column = @Column(name = "shipping_city")),
        @AttributeOverride(name = "state", column = @Column(name = "shipping_state")),
        @AttributeOverride(name = "zipcode", column = @Column(name = "shipping_zipcode"))
    })
    private Address shippingAddress;
}

CRUD Operations with Embeddable Types

Create Example

Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();

Address address = new Address();
address.setStreet("123 Main St");
address.setCity("Bangalore");
address.setState("Karnataka");
address.setZipcode("560001");

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

session.persist(user);
tx.commit();
session.close();

Read Example

User user = session.get(User.class, 1L);
System.out.println(user.getAddress().getCity()); // Bangalore

Update Example

session.beginTransaction();
User user = session.get(User.class, 1L);
user.getAddress().setCity("Lucknow");
session.update(user);
session.getTransaction().commit();

Delete Example

session.beginTransaction();
User user = session.get(User.class, 1L);
session.remove(user);
session.getTransaction().commit();

Hibernate Session, SessionFactory, and Transactions

  • SessionFactory: Configures Hibernate and provides Session objects.
  • Session: Represents a unit of work with the database.
  • Transaction: Ensures data consistency during CRUD operations.

For embeddable types, transactions work the same way as for normal entities.


Fetching and Performance Considerations

  • Embeddable types are always eagerly fetched with their parent entity since they reside in the same table.
  • This avoids extra SQL joins but may increase row size.
  • Best practice: Use embeddables only when fields always belong together.

Real-World Use Cases

  1. Addresses (Billing/Shipping) in e-commerce applications.
  2. Audit Information like createdBy, createdAt, updatedBy, updatedAt.
  3. Contact Information like email, phone, socialHandles.
  4. Monetary Values like currency + amount encapsulated together.

Common Pitfalls and Anti-Patterns

  • Avoid using embeddables for fields that change independently of the parent entity.
  • Do not overuse @AttributeOverrides as it can clutter your entity mappings.
  • Large embeddables can make queries slower due to wide tables.

Best Practices

  • Keep embeddables small and focused.
  • Reuse embeddables across entities to reduce duplication.
  • Use embeddables for value objects that naturally belong together.
  • Combine with Spring Boot JPA for clean integration.

📌 Hibernate Version Notes

Hibernate 5.x

  • Uses javax.persistence namespace.
  • Classic SessionFactory setup via hibernate.cfg.xml.
  • @Embeddable and @Embedded fully supported.

Hibernate 6.x

  • Migrated to jakarta.persistence namespace.
  • Enhanced SQL support and query APIs.
  • Improved bootstrapping and metadata handling.

Conclusion and Key Takeaways

  • @Embeddable and @Embedded make your Hibernate mappings modular and reusable.
  • They are ideal for grouping related fields that do not need their own identity.
  • Always use them for value objects like addresses, audit fields, or money objects.
  • Avoid overuse to prevent cluttered entities and wide database tables.

FAQ

1. What’s the difference between Hibernate and JPA?
Hibernate is an implementation of JPA with additional features.

2. How does Hibernate caching improve performance?
It avoids hitting the database repeatedly by storing entities in memory.

3. What are the drawbacks of eager fetching?
It loads more data than needed, which can hurt performance.

4. How do I solve the N+1 select problem in Hibernate?
Use JOIN FETCH in HQL or adjust fetch strategies.

5. Can I use Hibernate without Spring?
Yes, you can use Hibernate standalone with XML or programmatic configuration.

6. What’s the best strategy for inheritance mapping?
Depends on use case: SINGLE_TABLE, JOINED, or TABLE_PER_CLASS.

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

8. How is Hibernate 6 different from Hibernate 5?
Uses jakarta.persistence, better query APIs, and improved bootstrapping.

9. Is Hibernate suitable for microservices?
Yes, but use cautiously due to session and caching complexities.

10. When should I not use Hibernate?
Avoid it in extremely high-performance apps requiring raw SQL tuning.