Embedded types
Some fields belong together: an email and a website make up the contact details of an author, a city and a street make up an address. Such a group can be put into a separate class and embedded into an entity. The embedded class has no table and no id of its own: its fields are stored as ordinary columns of the owning entity's table. Since Hibernate 6.2 an embedded type can be a record.
The embedded type
// src/main/java/com/example/blog/Contact.java
package com.example.blog;
import jakarta.persistence.Embeddable;
@Embeddable
public record Contact(String email, String website) {
}@Embeddable marks a class that can be embedded. Unlike an entity, a record is fine here: the value is replaced as a whole, so it does not need to be changed field by field.
The Author entity
// src/main/java/com/example/blog/Author.java
package com.example.blog;
import jakarta.persistence.Embedded;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
@Entity
public class Author {
@Id
@GeneratedValue
private Long id;
private String name;
@Embedded
private Contact contact;
protected Author() {
}
public Author(String name, Contact contact) {
this.name = name;
this.contact = contact;
}
public String getName() {
return name;
}
public Contact getContact() {
return contact;
}
public void setContact(Contact contact) {
this.contact = contact;
}
}Add <class>com.example.blog.Author</class> to persistence.xml. The Contact record does not need to be listed — it is not an entity.
Saving and searching
// src/main/java/com/example/blog/Main.java
emf.runInTransaction(em -> em.persist(
new Author("Anna", new Contact("anna@example.com", "https://anna.example.com"))));
Author author = emf.callInTransaction(em -> em
.createQuery("select a from Author a where a.contact.email = :email", Author.class)
.setParameter("email", "anna@example.com")
.getSingleResult());
IO.println(author.getName() + ": " + author.getContact());In a query the embedded fields are reached through a dot: a.contact.email. Output:
Hibernate: create table Author (id bigint not null, email varchar(255), name varchar(255), website varchar(255), primary key (id))
Hibernate: insert into Author (email,website,name,id) values (?,?,?,?)
Hibernate: select a1_0.id,a1_0.email,a1_0.website,a1_0.name from Author a1_0 where a1_0.email=?
Anna: Contact[email=anna@example.com, website=https://anna.example.com]The table has the email and website columns directly — there is no separate table for contacts.
Changing the value
A record cannot be changed, so a new value replaces the old one:
// src/main/java/com/example/blog/Main.java
emf.runInTransaction(em -> {
Author author = em.find(Author.class, 1L);
author.setContact(new Contact("anna@example.org", author.getContact().website()));
});At commit Hibernate sees the new object and runs update Author set email=?,website=?,name=? where id=?.
Renaming columns
The column names come from the record components. To change one of them, put @AttributeOverride next to @Embedded:
// src/main/java/com/example/blog/Author.java
@Embedded
@AttributeOverride(name = "email", column = @Column(name = "contact_email", unique = true))
private Contact contact;The table now has contact_email varchar(255) unique. The same technique lets you embed one type twice, for example a billing and a shipping address.
Common mistake
When all columns of an embedded value are null, Hibernate reads the whole value back as null:
// src/main/java/com/example/blog/Main.java
emf.runInTransaction(em -> em.persist(new Author("Oleg", new Contact(null, null))));
Author oleg = emf.callInTransaction(em -> em.find(Author.class, 1L));
IO.println(oleg.getContact().email());Exception in thread "main" java.lang.NullPointerException: Cannot invoke "com.example.blog.Contact.email()" because the return value of "com.example.blog.Author.getContact()" is nullCheck the embedded value for null before using it.