How @EmbeddedId and @MapsId Work Together to Handle Complex Composite Keys in JPA
In the world of relational databases, it’s not uncommon to encounter tables where the primary key isn’t just a single column — it’s a combination of two or more columns. These are called composite keys. They usually appear in many-to-many join ta...

I am Tuanh.net. As of 2024, I have accumulated 8 years of experience in backend programming. I am delighted to connect and share my knowledge with everyone.
1. Introduction — The Hidden Challenge Behind Composite Keys
1.1 Understanding @EmbeddedId — The Blueprint of a Composite Key
@EmbeddedId allows you to group multiple columns into a single embeddable object that acts as your entity’s primary key. Instead of having multiple @Id annotations, you define one embeddable class that contains all the key parts.
1.2 Example: Creating a Composite Key Using @EmbeddedId
Student and Course is captured in a table called Enrollment.
Enrollment row is uniquely identified by (student_id, course_id) — our composite key.
// 1️⃣ The Embeddable Key Class
@Embeddable
public class EnrollmentId implements Serializable {
private Long studentId;
private Long courseId;
public EnrollmentId() {}
public EnrollmentId(Long studentId, Long courseId) {
this.studentId = studentId;
this.courseId = courseId;
}
// equals() and hashCode() are critical for Hibernate to manage identity properly
@Override
public boolean equals(Object o) { / implementation omitted for brevity / }
@Override
public int hashCode() { / implementation omitted for brevity / }
}
EnrollmentId class is not an entity — it’s just a value object used to identify an Enrollment.
1.3 The Entity Class Using @EmbeddedId
@Entity
public class Enrollment {
@EmbeddedId
private EnrollmentId id;
private LocalDate enrolledDate;
// constructors, getters, setters...
}
studentId and courseId fields of EnrollmentId as part of the primary key in the enrollment table.But what if you also want to reference the Student and Course entities directly instead of just storing their IDs? That’s where @MapsId comes in.
2. Understanding @MapsId — The Elegant Bridge Between Relationships and Composite Keys
@EmbeddedId is the “blueprint” of the key, then @MapsId is the “glue” that ties it to other entities.@MapsId tells JPA that a foreign key column in this entity is also part of its primary key.
Enrollment, where the primary key includes foreign keys to other entities.
2.1 Example: Using @MapsId in Practice
Student and Course entities.
@Entity
public class Enrollment {
@EmbeddedId
private EnrollmentId id;
@ManyToOne
@MapsId("studentId") // maps the studentId attribute of embedded id
@JoinColumn(name = "student_id")
private Student student;
@ManyToOne
@MapsId("courseId") // maps the courseId attribute of embedded id
@JoinColumn(name = "course_id")
private Course course;
private LocalDate enrolledDate;
public Enrollment() {}
public Enrollment(Student student, Course course) {
this.student = student;
this.course = course;
this.id = new EnrollmentId(student.getId(), course.getId());
}
}
- The
@MapsId("studentId")tells JPA: “Use the student’s ID value to fill thestudentIdfield of theEnrollmentIdkey.” - Similarly,
@MapsId("courseId")does the same for the course.
2.2 Why @MapsId Solves Real Problems
@MapsId, you’d have to manually synchronize the EnrollmentId fields with your entity associations — a perfect recipe for bugs when dealing with lazy loading or detached entities.
insert into enrollment (student_id, course_id, enrolled_date)
values (1, 10, '2025-10-29');
2.3 Debugging Tip: The “TransientPropertyValueException” Trap
object references an unsaved transient instance
Student, Course) haven’t been persisted yet before you try to save Enrollment.To fix this, make sure both parent entities are saved (and have their IDs assigned) before creating the enrollment record.
studentRepository.save(student);
courseRepository.save(course);
enrollmentRepository.save(new Enrollment(student, course));
3. Bonus Section — When to Avoid Composite Keys Altogether
id columns), managing relationships and migrations becomes easier.
Enrollment, OrderDetail, EmployeeProject), using @EmbeddedId + @MapsId is both clean and semantically accurate — it expresses “identity by association.”
3.1 Practical Advice from the Field
- Always implement
equals()andhashCode()in your embeddable key class. - Prefer using immutable keys (no setters in
EnrollmentId). - Remember that JPA needs a default constructor for your embeddable.
- If performance is critical, index your foreign keys separately, since composite primary keys don’t always index efficiently.
4. Conclusion — The Harmony Between Keys and Relationships
@EmbeddedId defines what your key looks like, while @MapsId defines how it’s linked to other entities.Used together, they create a robust mapping for complex domain models where relationships themselves form the entity’s identity.
Read more at : How @EmbeddedId and @MapsId Work Together to Handle Complex Composite Keys in JPA





