@ManyToMany Relationship in JPA
Fast recall
Quick revision
Core answer
Whether you understand join-table mechanics and know when @ManyToMany must become a junction entity
Putting @ManyToMany on both sides without mappedBy (creating duplicate join tables), or not knowing when to switch to a junction entity
Explain that the moment the link needs its own data (enrollment date, role) you decompose @ManyToMany into two @ManyToOne relationships
Interview-ready guide
Interview answer
Student and Course are a common example. Student can own the association with @JoinTable(name = "enrollment", ...), and Course can expose @ManyToMany(mappedBy = "courses") students. The owning side is a persistence mapping decision, not the side queried most often. Both collections should be updated by helper code. If enrollment needs a date, status, or grade, Enrollment should become a real entity; plain @ManyToMany cannot naturally hold those fields.
@ManyToMany Relationship in JPA
Direct Answer
@ManyToMany maps a relationship in which each side can be linked to many rows on the other side. A join table stores the two foreign keys. One side owns the mapping and can define @JoinTable; the other uses mappedBy. A Set is often convenient but is not required by JPA. If the link has data such as enrolledAt or grade, model it as an entity with two @ManyToOne relationships.
Code Example
@Entity
class Student {
@Id @GeneratedValue private Long id;
private String name;
// Owning side: defines the join table
@ManyToMany
@JoinTable(name = "student_course",
joinColumns = @JoinColumn(name = "student_id"),
inverseJoinColumns = @JoinColumn(name = "course_id"))
private Set<Course> courses = new HashSet<>(); // Set, not List
public void enroll(Course course) {
courses.add(course);
course.getStudents().add(this); // keep both sides in sync
}
}
@Entity
class Course {
@Id @GeneratedValue private Long id;
private String title;
@ManyToMany(mappedBy = "courses") // inverse side
private Set<Student> students = new HashSet<>();
public Set<Student> getStudents() { return students; }
}
// When the link needs its own data, use a junction entity instead:
@Entity
class Enrollment {
@EmbeddedId private EnrollmentId id;
@ManyToOne @MapsId("studentId") private Student student;
@ManyToOne @MapsId("courseId") private Course course;
private LocalDate enrollmentDate; // extra data a join table cannot hold
private String grade;
}
@Embeddable
record EnrollmentId(Long studentId, Long courseId) {}
Common Mistakes & Pitfalls
- Forgetting mappedBy — @ManyToMany on both Student and Course without mappedBy creates TWO join tables (
student_courseandcourse_student). Pick one owner and put mappedBy on the other. - Using List instead of Set — @ManyToMany with List makes Hibernate delete and re-insert all rows on every change. Use
Set. - Not synchronizing both sides — adding a course to
student.getCourses()without adding the student back breaks in-memory navigation. Always write helper methods.
Check your understanding
Answer these without looking back at the article.
- When should you replace @ManyToMany with a junction entity?
- What is the difference between @JoinTable and @JoinColumn?
- Why use Set instead of List for @ManyToMany?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.