Bird
Raised Fist0
Spring Bootframework~10 mins

@ManyToMany relationship in Spring Boot - Step-by-Step Execution

Choose your learning style10 modes available

Start learning this pattern below

Jump into concepts and practice - no test required

or
Recommended
Test this pattern10 questions across easy, medium, and hard to know if this pattern is strong
Concept Flow - @ManyToMany relationship
Define Entity A
Define Entity B
Add @ManyToMany in Entity A
Add @ManyToMany in Entity B
Create Join Table
Save Entities
Entities linked via Join Table
This flow shows how two entities are linked with a @ManyToMany annotation, creating a join table to connect them.
Execution Sample
Spring Boot
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import java.util.Set;

@Entity
public class Student {
  @Id
  private Long id;

  @ManyToMany
  Set<Course> courses;

  // getters and setters
}

@Entity
public class Course {
  @Id
  private Long id;

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

  // getters and setters
}
Defines two entities Student and Course linked by a @ManyToMany relationship with a join table.
Execution Table
StepActionEntity StateJoin Table StateResult
1Create Student s1Student s1 with empty courses setEmptyStudent s1 ready
2Create Course c1Course c1 with empty students setEmptyCourse c1 ready
3Add c1 to s1.coursess1.courses = {c1}EmptyLink started
4Add s1 to c1.studentsc1.students = {s1}EmptyLink confirmed
5Save s1 and c1Entities savedJoin table has entry (s1_id, c1_id)Relationship persisted
6Fetch s1s1.courses = {c1}Join table unchangedRelationship loaded
7Fetch c1c1.students = {s1}Join table unchangedRelationship loaded
💡 Execution stops after entities are saved and relationship is persisted in join table.
Variable Tracker
VariableStartAfter Step 3After Step 4After Step 5Final
s1.courses{}{c1}{c1}{c1}{c1}
c1.students{}{}{s1}{s1}{s1}
Join TableEmptyEmptyEmpty{(s1_id, c1_id)}{(s1_id, c1_id)}
Key Moments - 3 Insights
Why do we add the relationship on both sides (Student and Course)?
Because @ManyToMany is bidirectional, both sides must reference each other to keep the in-memory objects consistent, as shown in steps 3 and 4 of the execution_table.
What is the role of the join table in a @ManyToMany relationship?
The join table stores pairs of IDs linking the two entities, as seen in step 5 where saving entities creates the join table entry.
Why do we use 'mappedBy' on one side of the relationship?
'mappedBy' tells JPA which side owns the relationship to avoid duplicate join tables, shown in the code sample where Course uses mappedBy="courses".
Visual Quiz - 3 Questions
Test your understanding
Look at the execution_table, what is the state of s1.courses after step 3?
A{}
B{c1}
C{s1}
Dnull
💡 Hint
Check the 'Entity State' column for step 3 in the execution_table.
At which step does the join table get its first entry?
AStep 3
BStep 4
CStep 5
DStep 6
💡 Hint
Look at the 'Join Table State' column in the execution_table.
If we remove 'mappedBy' from Course, what would happen?
ATwo join tables would be created
BNo join table would be created
CThe relationship would be unidirectional
DEntities would not save
💡 Hint
Recall the explanation in key_moments about 'mappedBy' controlling ownership.
Concept Snapshot
@ManyToMany relationship links two entities with multiple connections.
Use @ManyToMany on both sides; one side owns the relation with 'mappedBy'.
JPA creates a join table storing pairs of linked entity IDs.
Add related entities to each other's collections to keep in-memory state consistent.
Save entities to persist the relationship in the join table.
Full Transcript
This visual execution traces the @ManyToMany relationship in Spring Boot. First, two entities, Student and Course, are created with empty sets for their relationships. Then, Course c1 is added to Student s1's courses set, and Student s1 is added to Course c1's students set, establishing a bidirectional link. When both entities are saved, JPA creates a join table entry linking their IDs. Fetching either entity later loads the related entities through this join table. The 'mappedBy' attribute on one side indicates the owning side to avoid duplicate join tables. This process ensures multiple Students can link to multiple Courses and vice versa, with the join table managing these connections.

Practice

(1/5)
1. What does the @ManyToMany annotation represent in Spring Boot?
easy
A. A relationship where entities inherit from each other.
B. A relationship where one entity has only one of the other entity.
C. A relationship where entities are unrelated.
D. A relationship where each entity can have many of the other entity.

Solution

  1. Step 1: Understand the meaning of @ManyToMany

    The annotation defines a link where each entity can be related to many instances of the other entity.
  2. Step 2: Compare with other relationship types

    Unlike one-to-one or one-to-many, many-to-many allows multiple connections on both sides.
  3. Final Answer:

    A relationship where each entity can have many of the other entity. -> Option D
  4. Quick Check:

    @ManyToMany = many-to-many link [OK]
Hint: Think: many items linked to many others [OK]
Common Mistakes:
  • Confusing with one-to-one or one-to-many
  • Thinking it means inheritance
  • Ignoring the bidirectional nature
2. Which of the following is the correct way to define a join table in a @ManyToMany relationship?
easy
A. @JoinColumn(name = "student_course")
B. @JoinTable(name = "student_course", joinColumns = @JoinColumn(name = "student_id"), inverseJoinColumns = @JoinColumn(name = "course_id"))
C. @Table(name = "student_course")
D. @JoinTable(name = "student_course", joinColumns = @JoinColumn(name = "course_id"), inverseJoinColumns = @JoinColumn(name = "student_id"))

Solution

  1. Step 1: Identify correct @JoinTable usage

    The join table must specify the table name and correctly assign joinColumns and inverseJoinColumns to the owning and inverse sides.
  2. Step 2: Check column names match entities

    joinColumns should refer to the current entity's ID, inverseJoinColumns to the other entity's ID.
  3. Final Answer:

    @JoinTable(name = "student_course", joinColumns = @JoinColumn(name = "student_id"), inverseJoinColumns = @JoinColumn(name = "course_id")) -> Option B
  4. Quick Check:

    @JoinTable with correct joinColumns = A [OK]
Hint: joinColumns = this entity, inverseJoinColumns = other entity [OK]
Common Mistakes:
  • Swapping joinColumns and inverseJoinColumns
  • Using @JoinColumn instead of @JoinTable
  • Omitting joinColumns or inverseJoinColumns
3. Given the following code snippet, what will be the output when printing student.getCourses().size() after adding two courses to the student?
@Entity
class Student {
  @ManyToMany
  Set<Course> courses = new HashSet<>();

  public Set<Course> getCourses() { return courses; }
}

@Entity
class Course {}

Student student = new Student();
Course c1 = new Course();
Course c2 = new Course();
student.getCourses().add(c1);
student.getCourses().add(c2);
System.out.println(student.getCourses().size());
medium
A. 2
B. 0
C. 1
D. Compilation error

Solution

  1. Step 1: Understand the collection type and additions

    The courses field is a HashSet, which allows unique elements. Adding two different Course objects increases size to 2.
  2. Step 2: Confirm no errors in adding elements

    Adding elements to the set is valid and no exceptions occur here.
  3. Final Answer:

    2 -> Option A
  4. Quick Check:

    Two courses added = size 2 [OK]
Hint: HashSet size equals unique added elements [OK]
Common Mistakes:
  • Assuming size is 0 because of missing persistence
  • Confusing with list allowing duplicates
  • Expecting compilation error due to missing annotations
4. Identify the error in this @ManyToMany mapping:
@Entity
class Author {
  @ManyToMany
  Set<Book> books;
}

@Entity
class Book {
  @ManyToMany(mappedBy = "books")
  Set<Author> authors;
}
medium
A. The @ManyToMany annotation is missing @JoinTable on both sides.
B. The mappedBy attribute is incorrectly used on the owning side.
C. The collections are not initialized, causing NullPointerException.
D. The entities must extend a common superclass.

Solution

  1. Step 1: Check collection initialization

    The sets 'books' and 'authors' are declared but not initialized, so they are null by default.
  2. Step 2: Understand impact of null collections

    Trying to add or access elements will cause NullPointerException at runtime.
  3. Final Answer:

    The collections are not initialized, causing NullPointerException. -> Option C
  4. Quick Check:

    Uninitialized sets cause null errors [OK]
Hint: Always initialize collections to avoid null errors [OK]
Common Mistakes:
  • Assuming @JoinTable is mandatory on both sides
  • Confusing owning side with inverse side
  • Thinking inheritance is required
5. You have two entities, Student and Club, with a @ManyToMany relationship. You want to add a new club to a student and ensure both sides reflect this change. Which code snippet correctly updates both sides?
hard
A. student.getClubs().add(club); club.getStudents().add(student);
B. student.getClubs().add(club);
C. club.getStudents().add(student);
D. student.setClubs(Set.of(club));

Solution

  1. Step 1: Understand bidirectional @ManyToMany updates

    Both sides must be updated to keep the relationship consistent in memory.
  2. Step 2: Check which code updates both sides

    student.getClubs().add(club); club.getStudents().add(student); adds the club to the student's clubs and the student to the club's students, syncing both sides.
  3. Final Answer:

    student.getClubs().add(club); club.getStudents().add(student); -> Option A
  4. Quick Check:

    Update both sides for consistency [OK]
Hint: Always update both sides of @ManyToMany [OK]
Common Mistakes:
  • Updating only one side causing stale data
  • Replacing collections without adding
  • Ignoring inverse side updates