Mastering Hibernate Relationships: The Ultimate Guide for 2025

Table of contents
- What Are Entity Relationships in Hibernate & JPA?
- The 4 Types of Hibernate Relationships
- @OneToOne: The Exclusive Pairing
- @OneToMany & @ManyToOne: The Most Common Relationship
- @ManyToMany: Handling Complex Connections
- Crucial Concepts You MUST Understand
- Hibernate Relationships: FAQ
- Conclusion & Key Takeaways
- Bonus!
- Happy coding!!!

As a Java Developer, modeling data is a core part of your Job. When working with JPA and Hibernate, correctly mapping the different entities of database plays a crucial role. Correctly mapping entity relationships is the key to clean, efficient and scalable application for an enterprise.
Incorrect mappings of entities can lead to nightmares, bugs and the database schema that is difficult to maintain due to its bad mapping of entities.
This in-depth guide will walk you through everything that you should know about JPA and Hibernate in 2025. This blog will cover the core concepts, copy-paste-ready code examples, and later dive into the crucial best practices that should be followed while mapping entities.
What Are Entity Relationships in Hibernate & JPA?
- In relational database, data is stored in form of tables, and one table is linked with another table via
FOREIGN KEY
which works as a reference key for that table. Now take this same concept in the Java, Hibernate is a framework in Java that is used to map different entities with each other using JPA. Different annotations are used to describe the links between entities directly into the code. Each entity is defined as Object class in Java and further these objects are linked with other objects via Hibernate and JPA Relationships.
The 4 Types of Hibernate Relationships
There are 4 different fundamental ways by which you can map Entity-A with Entity-B:
@OneToOne
: One A is linked to One B. (e.g., OneUser
have OneAddress
)@OneToMany
: One A is linked to Many B. (e.g., OneUser
have ManyPost
)@ManyToOne
: Many A’s are linked to One B. (e.g., ManyPost
have OneUser
)@ManyToMany
: Many A’s are linked to Many B’s. (e.g., ManyUser
have ManyGroup
)
Let's dive into how to implement each one.
@OneToOne
: The Exclusive Pairing
Use this when one record in the table is associated with exactly one record of another table.
Example: One
User
have One uniqueAddress
.Bidirectional
@OneToOne
example codeThe best practice is to make the relationship bidirectional, where each entity knows about the other. The side with the
FOREIGN KEY
is the relationship owning side.Now let’s implement this with One User have One Address relationship.
First, we will describe User.java file as attached below, where we are mapping Address class with
@OneToOne
annotation.@Data @Entity @Builder @NoArgsConstructor @AllArgsConstructor @Table(name = "users") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long userId; @NotNull @Column(unique = true) private String username; @ToString.Exclude @EqualsAndHashCode.Exclude @OneToOne(mappedBy = "user", cascade = CascadeType.ALL) private Address userAddress; }
Secondly, we will be defining the Address.java file where we will be mapping
User
class withAddress
class using@OneToOne
annotation.@Data @Entity @Builder @NoArgsConstructor @AllArgsConstructor public class Address { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long addressId; @NotNull private String street; @NotNull private String city; @NotNull private String zipCode; @OneToOne @ToString.Exclude @EqualsAndHashCode.Exclude @JoinColumn(name = "user_fk_id") private User user; }
Key Annotations Explained:
@OneToOne
: Defines the One To One association of two classes.@JoinColumn(name = “user_fk_id“)
: Describes the Foreign Key column for the Address table. This annotation is placed on the Owning side of the relationship.mappedBy = “user“
: Placed on the non-owning side of the relationships. It tells the Hibernate to look as theuser
field in theAddress
class to find the mapping configurations.cascade = CascadeType.ALL
: This tells Hibernate, whenever the non-owning side(User
class is non-owning side in our case) is saved, updated, merged or deleted, the owning side of relationship(Address
class in our case) should also be automatically saves, updated, merged or deleted.
@OneToMany
& @ManyToOne
: The Most Common Relationship
This relationship is the bread & butter of the data modeling. A
User
can have manyPost
, but eachPost
can be uploaded by only oneUser
. This is@OneToMany
relationship fromUser
toPost
and@ManyToOne
relationship fromPost
toUser
.Bidirectional
@OneToMany
/@ManyToOne
Example code:First things First, Let’s build User.java class as shown in below attached code block. The below code block show
@OneToMany
Relationship betweenUser
andPost
.@Data @Entity @Builder @NoArgsConstructor @AllArgsConstructor @Table(name = "users") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long userId; @NotNull @Column(unique = true) private String username; @Builder.Default @ToString.Exclude @EqualsAndHashCode.Exclude @OneToMany(mappedBy = "user", cascade = CascadeType.ALL) private List<Post> posts = new ArrayList<>(); }
Then after we will build Post.java file to Represent
@ManyToOne
relationship betweenPost
andUser
.@Data @Entity @Builder @NoArgsConstructor @AllArgsConstructor public class Post { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long postId; @NotNull private String title; @NotNull private String content; @ManyToOne @ToString.Exclude @EqualsAndHashCode.Exclude @JoinColumn(name = "users_fk_id") // Here 'fk' represents the 'Foreign Key'!! private User user; }
Key Best Practice: The
@ManyToOne
is almost always the Owning side of the Relationships because it is easy for the table having multiple rows related with the single foreign key to its “Parent“.
@ManyToMany
: Handling Complex Connections
Use this relationship when one table can be linked with many records of another table, and vice-versa.
Example: A
User
can ManyGroups
and, aGroup
can have ManyUsers
.This relationship requires a Third table in the database also known as a Join Table to store the Pairings. (e.g.,
user_group
join table withuser_id
andgroup_id
as keys, where one is primary key and another is foreign key.)Now Let’s follow up with a code block representing this relationship.
The attached below code block represents the User.java class.
@Data @Entity @Builder @NoArgsConstructor @AllArgsConstructor @Table(name = "users") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long userId; @NotNull @Column(unique = true) private String username; @Builder.Default @ToString.Exclude @EqualsAndHashCode.Exclude @ManyToMany(mappedBy = "users", cascade = CascadeType.ALL) private Set<Group> groups = new HashSet<>(); }
Let’s explore the Group.java class now.
@Data @Entity @Builder @NoArgsConstructor @AllArgsConstructor @Table(name = "groups") public class Group { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long groupId; @NotNull private String name; @ManyToMany @Builder.Default @ToString.Exclude @EqualsAndHashCode.Exclude @JoinTable( name = "user_groups", joinColumns = @JoinColumn(name = "group_id"), inverseJoinColumns = @JoinColumn(name = "user_fk_id") ) private Set<User> users = new HashSet<>(); }
Key Annotations Explained:
@JoinTable
: This annotations tells Hibernate to create a third table to store the pairings of two tables.joinColumns
: This joins as primary key of the class in which@JoinTable
annotation is used in to the newly created third table that will store the pairings of both the tables.inverseJoinColumns
: This joins as foreign key of the class in which@JoinTable
annotation is not used in to the newly created third table that will store the pairings of both the tables.
Crucial Concepts You MUST Understand
Getting the annotations right is only half the battle. These concepts are vital for performance and correctness.
The
mappedBy
Attribute ExplainedQuestion: What does
mappedBy
do in Hibernate?Answer: In a bidirectional relationship,
mappedBy
is used on the inverse (non-owning) side to indicate that the other side is responsible for managing the relationship. The value ofmappedBy
is the name of the field on the owning side that defines the mapping. It tells Hibernate "Don't create a foreign key column for this field; the mapping is handled elsewhere."
Cascading Operations (
CascadeType
)Question: What is
CascadeType
in JPA?Answer: It defines what happens to a related entity when an operation (like save, update, or delete) is performed on its owner.
CascadeType.PERSIST
: When you save the parent, the child is saved too.CascadeType.MERGE
: When you update the parent, the child is updated.CascadeType.REMOVE
: When you delete the parent, the child is deleted.CascadeType.ALL
: Includes all cascade operations. Use with caution!
Fetching Strategies:
LAZY
vsEAGER
and the N+1 ProblemThis is the single most important performance concept for Hibernate relationships.
FetchType.LAZY
(Best Practice): Hibernate will only load the related entities from the database when you explicitly access them (e.g., by callingauthor.getBooks()
). This is the default for collection-based relationships (@OneToMany
,@ManyToMany
). Always prefer LAZY fetching for collections.FetchType.EAGER
(Use with Caution): Hibernate will load the related entities at the same time it loads the parent entity. This is the default for single-entity relationships (@OneToOne
,@ManyToOne
). While sometimes convenient, it can lead to the infamous N+1 Query Problem.
What is the N+1 Query Problem?
Imagine you fetch a list of 100
Author
entities (1
query). If thebooks
collection isEAGER
, Hibernate will then execute a separate query for each author to fetch their books, resulting in 100 additional queries (N
queries). Total:1 + N = 101
queries! This is incredibly inefficient.LAZY
fetching avoids this by only fetching books for an author when you need them.
Hibernate Relationships: FAQ
Q1: Which side should be the "owning side" in a relationship?
- In
@OneToMany
, the@ManyToOne
side should always be the owner. For@OneToOne
and@ManyToMany
, it's your design choice, but be consistent. The owning side is the one where the foreign key or join table is defined.
- In
Q2: Why use
Set
instead ofList
for@ManyToMany
?- A
Set
is an unordered collection of unique elements. This perfectly models a many-to-many relationship, preventing duplicate associations and often performing better.
- A
Q3: I'm getting a
StackOverflowError
in mytoString()
method. Why?- This happens with bidirectional relationships. If
Author.toString()
prints its list of books, andBook.toString()
prints its author, they will call each other infinitely. Exclude the related entities from yourtoString()
methods by using the annotation of@ToString.Exclude
.
- This happens with bidirectional relationships. If
Conclusion & Key Takeaways
You now have a solid foundation for mapping any entity relationship in Hibernate.
Remember these golden rules:
Clearly Define Ownership: Use
@JoinColumn
on the owning side andmappedBy
on the inverse side.Default to LAZY Fetching: Especially for collections, to avoid the N+1 problem and ensure good performance.
Use Cascading Carefully:
CascadeType.ALL
is convenient but can lead to unintentional data deletion. Think about the entity lifecycle.Use
Set
for@ManyToMany
: It's a more appropriate data structure.
By following these guidelines, you'll build robust, performant, and maintainable data layers in your Java applications.
Bonus!
To see these Hibernate and JPA relationships in a real project, check out my simple social media backend service on GitHub. I highly recommend looking at the code to see how it all works.
Happy coding!!!
Subscribe to my newsletter
Read articles from Harshil Champaneri directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Harshil Champaneri
Harshil Champaneri
Passionate Java Full Stack Developer | Expert in Java, Spring Boot, Spring AI, Hibernate, Spring Security | Skilled in MySQL, PostgreSQL, React.js, Tailwind CSS | Core Skills: DSA, OOP, DBMS, OS