Many-to-Many Relationships and Join Tables in JPA

Site Admin · 11 Sep 2026 · 11 views

Many-to-Many Relationships and Join Tables in JPA

In relational databases, a many-to-many relationship occurs when rows in one table can be linked to many rows in another, and vice versa. Students enroll in courses, products belong to categories, users have roles. JPA models this with @ManyToMany and a join table that sits between the two entities.

How the Join Table Works

A many-to-many relationship requires a third table - the join table - that holds pairs of foreign keys. JPA can manage this table automatically so you never write INSERT statements for it yourself.

+------------------+     +---------------------+     +------------------+
|    students      |     | enrollments (join)  |     |    courses       |
+------------------+     +---------------------+     +------------------+
| * id BIGINT PK  |     | student_id FK       | --> | * id BIGINT PK   |
|   name VARCHAR   | --> | course_id FK        |     |   title VARCHAR  |
+------------------+     +---------------------+     |   credits INT    |
                                                     +------------------+

On the left side, a student can appear in many rows of the join table. On the right side, a course can also appear in many rows. Together, this forms the many-to-many relationship.

Defining the Entities

You annotate both sides with @ManyToMany. One side must be designated as the owning side - this is the side whose changes actually update the join table. The other side uses mappedBy to indicate it is the inverse side:

@Entity
@Table(name = "students")
public class Student {

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

    private String name;

    @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
    @JoinTable(
        name = "enrollments",
        joinColumns = @JoinColumn(name = "student_id"),
        inverseJoinColumns = @JoinColumn(name = "course_id")
    )
    private Set<Course> courses = new HashSet<>();

    // getters and setters
}

@Entity
@Table(name = "courses")
public class Course {

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

    private String title;

    private int credits;

    @ManyToMany(mappedBy = "courses")
    private Set<Student> students = new HashSet<>();

    // getters and setters
}

The @JoinTable annotation lets you name the join table and specify which columns hold each side of the foreign key. If you omit it, Hibernate generates a default table name like student_courses. The owning side (Student) controls the join table writes. Changes on the inverse side (Course) are ignored by Hibernate.

Adding and Removing Associations

Since the join table is managed by the ORM, adding an association is as simple as adding an element to the set. Hibernate generates the INSERT for the join table automatically:

Student alice = entityManager.find(Student.class, 1L);
Course math = entityManager.find(Course.class, 10L);

// Add enrollment - Hibernate inserts into the join table
alice.getCourses().add(math);

// Remove enrollment - Hibernate deletes from the join table
alice.getCourses().remove(math);

Always operate on the owning side. If you add a student to a course on the inverse side, Hibernate will not persist that change to the join table.

Real-World Scenario

In a university system, thousands of students enroll in hundreds of courses each semester. The enrollment join table is the backbone of the system. With JPA, you load a student and navigate directly to their courses without writing a single JOIN query. The ORM fetches the join table and the related courses behind the scenes.

Key Points

  • Many-to-many relationships require a join table to link the two entities.
  • Use @ManyToMany on both sides and @JoinTable on the owning side.
  • The owning side controls writes to the join table; the inverse side uses mappedBy.
  • Adding or removing elements from the collection automatically manages the join table rows.
  • Use Set instead of List to avoid duplicate entries in the join table.
Share this post:

Comments (0)

Please login or register to comment.