One-to-One Mappings
Learn how to model a one-to-one relationship in Hibernate with @OneToOne, using a Student and StudentProfile example.
Introduction
So far every entity you have mapped has stood alone. Real applications, though, are full of entities that relate to one another. The simplest relationship to start with is one-to-one: exactly one row in one table corresponds to exactly one row in another. A classic example is a Student and a StudentProfile — every student has exactly one profile containing extended details like a bio and a photo URL, and that profile belongs to exactly one student.
- How to model a one-to-one relationship with @OneToOne
- The difference between the owning side and the inverse side
- How to save two related entities together
- How to fetch an entity along with its related entity
Modeling the Relationship
We will split student data into two tables: students holds core fields, and student_profiles holds extended details, linked back to students by a foreign key.
@Entity@Table(name = "students")public class Student {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id;
@Column(name = "name", nullable = false) private String name;
@OneToOne(mappedBy = "student", cascade = CascadeType.ALL) private StudentProfile profile;
public Student() {} public Student(String name) { this.name = name; }
public Long getId() { return id; } public String getName() { return name; } public StudentProfile getProfile() { return profile; } public void setProfile(StudentProfile profile) { this.profile = profile; }}@Entity@Table(name = "student_profiles")public class StudentProfile {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id;
@Column(name = "bio") private String bio;
@Column(name = "photo_url") private String photoUrl;
@OneToOne @JoinColumn(name = "student_id", referencedColumnName = "id") private Student student;
public StudentProfile() {}
public StudentProfile(String bio, String photoUrl) { this.bio = bio; this.photoUrl = photoUrl; }
public Long getId() { return id; } public String getBio() { return bio; } public String getPhotoUrl() { return photoUrl; } public Student getStudent() { return student; } public void setStudent(Student student) { this.student = student; }}Owning Side vs Inverse Side
In a bidirectional relationship, exactly one side owns the foreign key column and the other side simply mirrors it. Here, StudentProfile is the owning side — it declares @JoinColumn(name = "student_id"), meaning the student_profiles table gets a student_id column. Student is the inverse side, marked with mappedBy = "student", pointing at the field name on the owning side that maps back to it.
The entity annotated with @JoinColumn owns the relationship and controls the foreign key. The entity annotated with mappedBy is the inverse side — it is read-only for the relationship and simply reflects what the owning side has set.
| Side | Entity | Annotation | Controls the FK column? |
|---|---|---|---|
| Owning side | StudentProfile | @OneToOne @JoinColumn(name = "student_id") | Yes |
| Inverse side | Student | @OneToOne(mappedBy = "student") | No — mirrors the owning side |
Saving a One-to-One Relationship
Because Student declares CascadeType.ALL on the profile field, saving a Student automatically saves its associated StudentProfile in the same operation — as long as you set the relationship on both sides.
Session session = sessionFactory.openSession();Transaction tx = session.beginTransaction();
Student student = new Student("Priya Nair");StudentProfile profile = new StudentProfile("Aspiring backend developer.", "https://example.com/priya.jpg");
// Link both sides of the relationshipstudent.setProfile(profile);profile.setStudent(student);
session.persist(student); // cascades and persists the profile too
tx.commit();session.close();Click Run to see what this code prints.
Setting only student.setProfile(profile) without profile.setStudent(student) leaves the owning side's foreign key null, because Hibernate reads the relationship state from the owning side (StudentProfile) when generating SQL.
Fetching the Related Entity
Once saved, loading a Student gives you direct access to its profile through the object graph — no manual join required in your Java code.
Session session = sessionFactory.openSession();
Student student = session.get(Student.class, 1L);StudentProfile profile = student.getProfile();
System.out.println(student.getName() + ": " + profile.getBio());
session.close();Click Run to see what this code prints.
Common Mistakes
- Setting the relationship only on the inverse side (Student), leaving the owning side's foreign key unset.
- Putting @JoinColumn on both sides of the relationship — only the owning side should declare it.
- Forgetting mappedBy on the inverse side, which causes Hibernate to try to create two separate foreign key columns.
- Using CascadeType.ALL on both sides in a way that causes infinite recursion during equals()/hashCode() or toString().
Best Practices
- Decide which entity naturally owns the relationship and put @JoinColumn there.
- Always update both sides of a bidirectional relationship together, ideally through a helper method.
- Use cascade = CascadeType.ALL when the child (StudentProfile) has no independent lifecycle of its own.
- Consider a unidirectional @OneToOne (drop the mappedBy side) if you never need to navigate from profile back to student.
Frequently Asked Questions
Yes. If you never need to navigate from StudentProfile back to Student, you can omit the student field entirely and keep only the owning side with @JoinColumn.
EAGER by default, meaning the related entity is loaded immediately along with the owning entity. This differs from @OneToMany and @ManyToMany, which default to LAZY.
Yes, using @MapsId lets the child entity reuse the parent's primary key value instead of having its own separate foreign key column — a common pattern for true one-to-one extension tables.
Key Takeaways
- @OneToOne models a strict one-row-to-one-row relationship between two entities.
- The side with @JoinColumn owns the foreign key; the side with mappedBy is the inverse.
- Both sides of a bidirectional relationship must be set explicitly in your code.
- @OneToOne defaults to EAGER fetching, unlike collection-based relationships.
Summary
One-to-one relationships are the simplest association to model, but the owning-side/inverse-side rules you learned here apply to every relationship type ahead. Next, you will model the far more common one-to-many relationship — a single Department containing many Employees.