Entity Relationships & Associations

Owning vs Inverse Side (mappedBy)

18 min Lesson 5 of 13

Owning vs Inverse Side (mappedBy)

When you model a bidirectional association in JPA — for example, a Customer that holds a list of Order objects, and each Order that holds a reference back to its Customer — Hibernate must know which of the two Java fields corresponds to the actual foreign key column in the database. That is the single question answered by the owning side / inverse side distinction, and getting it wrong is one of the most common reasons developers see association changes silently ignored at flush time.

The Core Rule

In every bidirectional relationship there are exactly two sides:

  • The owning side — the field that carries the physical foreign key column. Hibernate reads this field when it decides what to write to the database. This side is annotated with @JoinColumn (for @ManyToOne / @OneToOne) or @JoinTable (for @ManyToMany).
  • The inverse side — the "mirror" field on the other entity. It exists only for navigating the Java object graph. Hibernate ignores changes made exclusively to the inverse side when writing to the database. This side is declared with the mappedBy attribute.
The mappedBy rule in one sentence: mappedBy = "fieldName" says "the foreign key for this relationship is managed by the field called fieldName on the other entity — I am just a read-only mirror."

Bidirectional @ManyToOne / @OneToMany Example

This is by far the most common bidirectional relationship. A Customer has many Order objects; each Order belongs to one Customer. The foreign key (customer_id) lives in the orders table, so the Order.customer field is the owning side.

// ---- Order.java (OWNING SIDE — holds the FK column) ---- @Entity @Table(name = "orders") public class Order { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String reference; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "customer_id") // ← declares the FK column private Customer customer; // getters, setters … } // ---- Customer.java (INVERSE SIDE — mappedBy mirrors Order.customer) ---- @Entity @Table(name = "customers") public class Customer { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; @OneToMany(mappedBy = "customer") // ← "I am managed by Order.customer" private List<Order> orders = new ArrayList<>(); // getters, setters … }

What Happens If You Only Set the Inverse Side

This is the classic trap. A developer adds an order to the customer's list but forgets to set the back-reference on the order:

// WRONG — only the inverse side is updated customer.getOrders().add(order); entityManager.persist(order); // Hibernate IGNORES the change to customer.orders at flush time. // customer_id in the orders row stays NULL.

Because customer.orders is the inverse side (it has mappedBy), Hibernate simply does not inspect it when generating SQL. The fix is always to set both sides:

// CORRECT — set the owning side (order.customer) order.setCustomer(customer); customer.getOrders().add(order); // keeps the in-memory graph consistent entityManager.persist(order); // Hibernate reads order.customer → INSERT INTO orders (customer_id, …) VALUES (…)
Encapsulate bidirectional sync in a helper method. Add a convenience method on the entity that always sets both sides together, and call only that method from application code:
// In Customer.java public void addOrder(Order order) { orders.add(order); // inverse side — keeps in-memory graph correct order.setCustomer(this); // owning side — what Hibernate actually writes }
This pattern eliminates an entire class of "my data isn't being saved" bugs.

Bidirectional @OneToOne

The same rule applies. Pick one entity to hold the FK column and mark the other with mappedBy:

@Entity public class Employee { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @OneToOne(fetch = FetchType.LAZY) @JoinColumn(name = "passport_id", unique = true) // owning side private Passport passport; } @Entity public class Passport { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @OneToOne(mappedBy = "passport") // inverse side private Employee employee; }

Bidirectional @ManyToMany

In a many-to-many association, the FK lives in a join table. Only one entity needs to define the join table with @JoinTable; the other uses mappedBy:

@Entity public class Student { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @ManyToMany @JoinTable( name = "student_course", joinColumns = @JoinColumn(name = "student_id"), inverseJoinColumns = @JoinColumn(name = "course_id") ) private Set<Course> courses = new HashSet<>(); } @Entity public class Course { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @ManyToMany(mappedBy = "courses") // inverse side — join table managed by Student private Set<Student> students = new HashSet<>(); }
Many-to-many: always update the owning side (Student.courses). Adding a student to course.students alone will never insert a row into the student_course join table because course.students is the inverse side. Always add the course to the student's set (or use a bidirectional helper method) so Hibernate sees the change on the owning side.

Choosing Which Side Owns the FK

For @ManyToOne / @OneToMany the choice is natural: the FK column is always on the "many" side of the table, so the @ManyToOne field in the "many" entity is always the owning side. You cannot reverse this without restructuring your schema.

For @OneToOne and @ManyToMany you have more freedom. A common convention is to place the owning side on the entity that is the more "dependent" or "child" one — the one that cannot exist without the other. This way the FK or join table naturally lives next to the data that depends on it.

Quick Reference: mappedBy Rules

  • Exactly one side in a bidirectional pair uses mappedBy; the other side owns the FK.
  • The value of mappedBy is the field name on the owning entity, not the column name.
  • The side with mappedBy must not also have @JoinColumn or @JoinTable — those go on the owning side.
  • Changes made only to the inverse side (mappedBy) are silently ignored by Hibernate at flush time.
  • Always synchronise both sides in memory, even if only the owning side drives the SQL — stale in-memory state leads to hard-to-debug second-level cache or within-transaction read bugs.

Summary

The owning side is the field with the foreign key column (annotated with @JoinColumn or @JoinTable). The inverse side is declared with mappedBy and is a pure Java navigation convenience — Hibernate writes nothing to the database based on it. Understanding this distinction prevents the most common JPA pitfall: updating only the inverse side and seeing nothing change in the database. Use helper methods that synchronise both sides together, and you will never be bitten by this again.