Many-to-many relationships are one of the most commonly used relationships with JPA. In this tutorial we’ll see how to map a many to many relationship with JPA and Hibernate, and how to avoid making some common mistakes.
Diagram
A many-to-many relationship occurs when multiple records in one table are associated with multiple records in another table. Generally, relational database systems do not allow you to implement a direct many-to-many relationship between two tables, because otherwise a many-to-many relationship between two tables would cause several problems.
To solve the association problem, you can break up the many-to-many relationship into two one-to-many relationships by using a third table, called an association table. Each record in an associative table includes a match field that contains the primary key value of the two tables it joins.

The user_role associative table contains as foreign keys the primary keys of the user and role tables.
Configuration
Dependencies
The dependencies for this project are clearly Hibernate and finally the H2 database.
<dependencies> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <version>6.2.4.Final</version> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>2.1.214</version> </dependency> </dependencies>
Hibernate
Let’s create the persistence.xml file, which is nothing more than the JPA persistence configuration. In this file we are going to define all the database connection parameters, we are also going to define where our entities are located.
<?xml version="1.0" encoding="UTF-8"?> <persistence xmlns="https://jakarta.ee/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://jakarta.ee/xml/ns/persistence https://jakarta.ee/xml/ns/persistence/persistence_3_0.xsd" version="3.0"> <persistence-unit name="default"> <class>com.lorenzomiscoli.hibernate_one_to_many.model.User</class> <class>com.lorenzomiscoli.hibernate_one_to_many.model.Role</class> <properties> <property name="jakarta.persistence.jdbc.driver" value="org.h2.Driver" /> <property name="jakarta.persistence.jdbc.url" value="jdbc:h2:mem:hibernate_many_to_many" /> <property name="jakarta.persistence.jdbc.user" value="sa" /> <property name="jakarta.persistence.jdbc.password" value="" /> <property name="hibernate.hbm2ddl.auto" value="update" /> </properties> </persistence-unit> </persistence>
@ManyToMany Mapping
First create the User entity.
@Entity @Table(name = "[user]") public class User { @GeneratedValue(strategy = GenerationType.IDENTITY) @Id private Integer id; private String username; @ManyToMany @JoinTable(name = "user_role", joinColumns = @JoinColumn(name = "user_id"), inverseJoinColumns = @JoinColumn(name = "role_id")) private Set<Role> roles = new HashSet<>(); public void addRole(Role role) { roles.add(role); role.getUsers().add(this); } public void removeRole(Role role) { roles.remove(role); role.getUsers().remove(this); } // getters and setters }
With the @ManyToMany annotation we define a many-to-many relationship. With the annotation @JoinTable we specify which is the associative table, while with @JoinColumn we specify which is the column that holds the foreign key.
At this point, let’s create the Role entity.
@Entity @Table(name = "role") public class Role { @GeneratedValue(strategy = GenerationType.IDENTITY) @Id private Integer id; private String name; @ManyToMany(mappedBy = "roles") Set<User> users = new HashSet<>(); // getters and setters }
We inserted the @ManyToMany annotation again but this time adding the mappedBy attribute. This way we tell JPA that the relationship is already mapped to the other side of the relationship, that is User.
As you can see we have created a bidirectional relationship, because the @ManyToMany annotation is on both entities, however the owning side of the relationship is User, because it holds the @JoinTable annotation.
Being a bidirectional relationship, we need to synchronize changes on both entities when we update or remove, so we created two utility methods (addRole and removeRole) on the User class.
We didn’t use the java.util.List class because Hibernate handles remove operations on many-to-many relationships mapped to java.util.List very inefficiently.
If we try to remove a Role from a User with java.util.List:
EntityManagerFactory emFactory = Persistence.createEntityManagerFactory("default"); EntityManager em = emFactory.createEntityManager(); em.getTransaction().begin(); User user = em.find(User.class, 2); Role role = em.find(Role.class, 4); user.removeRole(role); em.getTransaction().commit(); em.close(); emFactory.close();
Hibernate generates the following SQL statements:
Hibernate: delete from user_role where user_id=? Hibernate: insert into user_role (user_id, role_id) values (?, ?)
As you can see it first removes all the records from the association table and then inserts all the rest afterwards. This certainly affects performance so I recommend using java.util.Set to map many-to-many associations.
When we try to remove a Role from a User with java.util.Set Hibernate generates the following SQL statement:
Hibernate: delete from user_role where user_id=? and role_id=?
Hibernate handles remove operations on associations mapped with java.util.Set much better, it only removes the records expected from the association and keeps the others intact.
As for the FetchType we left the default one, which in a @ManyToMany association is LAZY by default, and should always remain LAZY in a many-to-many association. As for the CascadeType, I advise you not to use REMOVE or ALL in a many-to-many mapping because at best, it might just create performance issues, but at worst, it might also remove more records than you want.
Conclusion
In this tutorial we have seen what a many to many relationship is and how we can map it with JPA and Hiberante.
We have also seen the difference between using java.util.Set and java.util.List, and how easy it is to create performance problems with the incorrect mapping.
The source code of this project is available on GitHub.