Chaturmind
LearnDSASystem DesignInterview PrepDevOpsEngineering GrowthBlog
Start learning
Chaturmind

Structured learning paths for engineers who want to go deep. Written by practitioners.

Learn

  • Java
  • DSA
  • System Design
  • Spring Boot
  • AI / ML
  • DevOps
  • Engineering Growth
  • Java Interview Prep

Company

  • Blog
  • Contact

Legal

  • Privacy Policy
  • Terms of Service

© 2026 Chaturmind. All rights reserved.

Built for engineers who want to go deep.


← Java Interview Prep: 8+ Years (Senior & Lead)

Expert Core Java

  • Tricky Java Output, Operators & OOP Edge Cases — Interview Questions
  • Tricky Exceptions, Memory & Keyword Questions — Interview Questions
  • Classic Java Language Questions, Senior-Grade Answers — Interview Questions
  • Classic Collections, Threads & JDK APIs, Senior-Grade Answers — Interview Questions
  • Reflection, Dynamic Proxies, final & Modern OOP Design — Interview Questions

JVM Internals & Performance

  • Class Loading, Bytecode & Object Layout — Interview Questions
  • JIT Compilation & Runtime Optimisations — Interview Questions
  • Garbage Collectors Deep Dive — Interview Questions
  • JVM Tuning, GC Logs & Memory Footprint — Interview Questions
  • Memory Leaks, OutOfMemoryErrors & Profiling Tools — Interview Questions
  • Modules, Agents & Advanced JVM APIs — Interview Questions

Collections & Concurrency at Scale

  • Collections Internals & Complexity — Interview Questions
  • Iterators, Comparators & Ordering Contracts — Interview Questions
  • Concurrent Collections, Queues & Lock-Free Structures — Interview Questions
  • Threads, Executors & ForkJoin Internals — Interview Questions
  • Locks, Atomics, CAS & Synchronizers — Interview Questions
  • Java Memory Model, volatile, Fences & ThreadLocal — Interview Questions
  • Deadlock, Livelock, Starvation & Concurrent Design — Interview Questions
  • CompletableFuture, Parallel Streams & Non-Blocking I/O — Interview Questions

Modern Java (8 to 21+)

  • Lambdas & Functional Interfaces Internals — Interview Questions
  • Streams & Collectors Deep Dive — Interview Questions
  • Optional & Interface Default/Static Methods — Interview Questions
  • Java 9–25 Features & Virtual Threads — Interview Questions

Design Patterns, SOLID & Clean Code

  • Design Pattern Trade-offs & Combinations — Interview Questions
  • SOLID, Clean Code & Anti-Patterns — Interview Questions

Spring & Spring Boot Internals

  • IoC, Dependency Injection & Bean Lifecycle Internals — Interview Questions
  • Spring AOP, Proxies & @Async Internals — Interview Questions
  • Spring Configuration, Auto-Configuration & Custom Starters — Interview Questions
  • Spring MVC & REST Internals, Exception Frameworks — Interview Questions
  • Spring Security Advanced Internals — Interview Questions
  • Spring WebFlux, Reactor & R2DBC — Interview Questions
  • Spring Cloud, Observability & Distributed Tracing — Interview Questions
  • Spring Boot 3, Native Images & Production Scenarios — Interview Questions

JPA, Hibernate & Databases at Scale

  • Spring Data JPA — Queries, Projections, Custom Repositories & Locking — Interview Questions
  • JPA Entity Mapping, Associations & Cascades — Interview Questions
  • JPQL vs Native Queries in Depth — Interview Questions
  • Hibernate Caching — First-Level, Second-Level & Query Cache — Interview Questions
  • Lazy vs Eager Loading, LazyInitializationException & N+1 — Interview Questions
  • JPA Transactions, Propagation, Isolation & Dirty Checking — Interview Questions
  • SQL vs NoSQL, Indexing & Query Tuning — Interview Questions
  • Database Scaling, Replication, Pooling & Consistency Models — Interview Questions
  • Redis, Search, Time-Series, CDC & Transactional Data Modelling — Interview Questions

Testing Strategy & API Design

  • Spring Boot Test Slices, Context & Test Strategy — Interview Questions
  • Testing Web, Persistence, Security, Async & Messaging in Spring Boot — Interview Questions
  • JUnit 5 & Mockito, Advanced — Interview Questions
Chaturmind
← Java Interview Prep: 8+ Years (Senior & Lead)

Expert Core Java

  • Tricky Java Output, Operators & OOP Edge Cases — Interview Questions
  • Tricky Exceptions, Memory & Keyword Questions — Interview Questions
  • Classic Java Language Questions, Senior-Grade Answers — Interview Questions
  • Classic Collections, Threads & JDK APIs, Senior-Grade Answers — Interview Questions
  • Reflection, Dynamic Proxies, final & Modern OOP Design — Interview Questions

JVM Internals & Performance

  • Class Loading, Bytecode & Object Layout — Interview Questions
  • JIT Compilation & Runtime Optimisations — Interview Questions
  • Garbage Collectors Deep Dive — Interview Questions
  • JVM Tuning, GC Logs & Memory Footprint — Interview Questions
  • Memory Leaks, OutOfMemoryErrors & Profiling Tools — Interview Questions
  • Modules, Agents & Advanced JVM APIs — Interview Questions

Collections & Concurrency at Scale

  • Collections Internals & Complexity — Interview Questions
  • Iterators, Comparators & Ordering Contracts — Interview Questions
  • Concurrent Collections, Queues & Lock-Free Structures — Interview Questions
  • Threads, Executors & ForkJoin Internals — Interview Questions
  • Locks, Atomics, CAS & Synchronizers — Interview Questions
  • Java Memory Model, volatile, Fences & ThreadLocal — Interview Questions
  • Deadlock, Livelock, Starvation & Concurrent Design — Interview Questions
  • CompletableFuture, Parallel Streams & Non-Blocking I/O — Interview Questions

Modern Java (8 to 21+)

  • Lambdas & Functional Interfaces Internals — Interview Questions
  • Streams & Collectors Deep Dive — Interview Questions
  • Optional & Interface Default/Static Methods — Interview Questions
  • Java 9–25 Features & Virtual Threads — Interview Questions

Design Patterns, SOLID & Clean Code

  • Design Pattern Trade-offs & Combinations — Interview Questions
  • SOLID, Clean Code & Anti-Patterns — Interview Questions

Spring & Spring Boot Internals

  • IoC, Dependency Injection & Bean Lifecycle Internals — Interview Questions
  • Spring AOP, Proxies & @Async Internals — Interview Questions
  • Spring Configuration, Auto-Configuration & Custom Starters — Interview Questions
  • Spring MVC & REST Internals, Exception Frameworks — Interview Questions
  • Spring Security Advanced Internals — Interview Questions
  • Spring WebFlux, Reactor & R2DBC — Interview Questions
  • Spring Cloud, Observability & Distributed Tracing — Interview Questions
  • Spring Boot 3, Native Images & Production Scenarios — Interview Questions

JPA, Hibernate & Databases at Scale

  • Spring Data JPA — Queries, Projections, Custom Repositories & Locking — Interview Questions
  • JPA Entity Mapping, Associations & Cascades — Interview Questions
  • JPQL vs Native Queries in Depth — Interview Questions
  • Hibernate Caching — First-Level, Second-Level & Query Cache — Interview Questions
  • Lazy vs Eager Loading, LazyInitializationException & N+1 — Interview Questions
  • JPA Transactions, Propagation, Isolation & Dirty Checking — Interview Questions
  • SQL vs NoSQL, Indexing & Query Tuning — Interview Questions
  • Database Scaling, Replication, Pooling & Consistency Models — Interview Questions
  • Redis, Search, Time-Series, CDC & Transactional Data Modelling — Interview Questions

Testing Strategy & API Design

  • Spring Boot Test Slices, Context & Test Strategy — Interview Questions
  • Testing Web, Persistence, Security, Async & Messaging in Spring Boot — Interview Questions
  • JUnit 5 & Mockito, Advanced — Interview Questions
HomeLearnJava Interview PrepJava Interview Prep: 8+ Years (Senior & Lead)JPA, Hibernate & Databases at Scale
✓ FreeAdvanced· 9 min read

JPA Entity Mapping, Associations & Cascades — Interview Questions

Unidirectional vs bidirectional associations, the owning side and mappedBy, infinite recursion when serialising bidirectional entities, many-to-many with extra columns, @JoinColumn vs @JoinTable, cascading in one-to-one and parent-child hierarchies, CascadeType.ALL on both sides, orphan removal, default fetch types, and one-to-one with a shared primary key (@MapsId).

Published September 25, 2026


How to use this lesson

Mapping mistakes cause most JPA performance and correctness bugs: extra UPDATEs, orphaned rows, recursive JSON, and accidental cascaded deletes. For each answer, say which table owns the foreign key, and what SQL Hibernate will run.

Q1. What's the difference between unidirectional and bidirectional associations?

Short answer:

  • Unidirectional: only one entity has a reference (for example OrderLine.order with @ManyToOne, and no collection on Order). It's simpler, with fewer synchronisation bugs. Unidirectional @OneToMany without @JoinColumn creates a join table, and extra statements, which is usually a performance smell.
  • Bidirectional: both sides reference each other (Order.lines @OneToMany(mappedBy = "order") and OrderLine.order @ManyToOne). It's convenient for navigation both ways, but you must keep both sides in sync in code (helper methods), and watch out for recursive toString/JSON serialisation.

The best practice: model @ManyToOne on the child (the FK side) as the primary mapping. Add the parent's collection only if you genuinely navigate that way, and prefer queries over huge collections (@OneToMany on thousands of rows is a trap).

Q2. What does mappedBy mean, and when do you use it? Which side is the owning side, and why does it matter?

Short answer: In a bidirectional association, only one side controls the foreign key: the owning side. The other side declares mappedBy = "fieldOnOwningSide", meaning "this relationship is already mapped by that field; don't manage the FK from here".

  • @ManyToOne is always the owning side (the FK column lives in its table).
  • In a @OneToOne, the owner is the side with the @JoinColumn.
  • In a @ManyToMany, you choose one owner (with @JoinTable), and the other uses mappedBy.

Why it matters: Hibernate only looks at the owning side when writing the FK. If you only do order.getLines().add(line) (the inverse side) without line.setOrder(order), no FK is written (or it's set to null). If both sides were mapped as owners, you'd get duplicate updates, or two FK or join-table mappings.

@Entity class Order {
    @OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<OrderLine> lines = new ArrayList<>();
    public void addLine(OrderLine l) { lines.add(l); l.setOrder(this); }      // keep both sides in sync
    public void removeLine(OrderLine l) { lines.remove(l); l.setOrder(null); }
}
@Entity class OrderLine {
    @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "order_id", nullable = false)
    private Order order;                                                       // the owning side
}

Q3. How do you avoid infinite recursion in bidirectional mappings, especially when serialising to JSON?

Short answer: Order → lines → order → lines… makes Jackson (and Lombok @ToString/@EqualsAndHashCode) recurse until a StackOverflowError. The fixes:

  • Best: don't serialise entities. Map them to DTOs or records containing exactly what the API needs. That also avoids lazy-loading exceptions, over-fetching and schema coupling.
  • With Jackson on entities:
    • @JsonManagedReference/@JsonBackReference (the forward part is serialised, and the back reference is omitted);
    • @JsonIgnore on the back side;
    • @JsonIdentityInfo (serialises repeated objects as IDs).
  • Lombok: exclude the association fields from @ToString and @EqualsAndHashCode, or don't use @Data on entities.

Q4. How do you map a many-to-many relationship with extra columns?

Short answer: A plain @ManyToMany join table can't hold extra columns. Promote the join table to an entity, with two @ManyToOnes. For example, Enrollment between Student and Course, with enrolledAt and grade:

@Entity
public class Enrollment {
    @EmbeddedId private EnrollmentId id = new EnrollmentId();
    @ManyToOne(fetch = FetchType.LAZY) @MapsId("studentId") private Student student;
    @ManyToOne(fetch = FetchType.LAZY) @MapsId("courseId")  private Course course;
    private Instant enrolledAt;
    private String grade;
}
@Embeddable public record EnrollmentId(Long studentId, Long courseId) implements Serializable {
    public EnrollmentId() { this(null, null); }                      // JPA needs a no-arg constructor
}
// Student: @OneToMany(mappedBy = "student") Set<Enrollment> enrollments;

Key points to cover:

  • A surrogate key (@Id @GeneratedValue Long id, plus a unique constraint on (student_id, course_id)) is often simpler than a composite key.
  • Prefer Set for many-to-many collections (a List "bag" causes delete-all-and-reinsert behaviour on changes).

Q5. What's the difference between @JoinColumn and @JoinTable?

Short answer:

  • @JoinColumn: the association is stored as a foreign-key column in one of the entities' tables (order_line.order_id). It's the norm for @ManyToOne, @OneToOne, and a unidirectional @OneToMany (with the FK in the child table).
  • @JoinTable: the association lives in a separate link table (student_course(student_id, course_id)). It's required for @ManyToMany, and optional for one-to-many or one-to-one (for example, when you can't change the child table, or the relationship is optional and sparse).

Join tables add a join, and extra inserts. Use them when the model needs them.

Q6. Can you cascade operations in a one-to-one relationship? How do you cascade across a parent-child hierarchy?

Short answer: Yes. Cascades work on any association: @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true) for a composition (User → UserProfile), where the child's lifecycle is owned by the parent. For aggregates (Order → OrderLines → LineDiscounts):

  • put cascade = {PERSIST, MERGE, REMOVE} (or ALL) plus orphanRemoval = true on the parent's collection;
  • cascade down each level;
  • manage the children only through the aggregate root (order.addLine(...)).

The rules:

  • Cascade from parent to child only, and only within an aggregate that shares a lifecycle.
  • Never cascade REMOVE on @ManyToOne/@ManyToMany: deleting an order line would delete the shared product or customer.

Large deletes are better done with bulk queries, or database ON DELETE CASCADE, than by loading everything and removing each entity one by one.

Q7. What happens if both sides of a relationship have cascade = CascadeType.ALL?

Short answer: Operations propagate in both directions. The dangerous one is REMOVE from the child side:

  • deleting one OrderLine cascades to its Order;
  • which cascades to all the other lines (and possibly on to the customer);
  • so you get mass unintended deletes, or EntityNotFound and constraint violations.

PERSIST/MERGE in both directions also cause confusing re-attachment of graphs, and extra work. Only the aggregate root cascades, and child-to-parent associations usually have no cascade.

Q8. How do you handle orphan removal?

Short answer: orphanRemoval = true on a @OneToMany or @OneToOne means that removing a child from the parent's collection (or nulling a one-to-one reference) deletes the child row at flush. It's for composition: children can't exist without their parent.

  • It's different from CascadeType.REMOVE, which deletes the children only when the parent is deleted. Orphan removal also triggers on disassociation.
  • Pitfalls:
    • Don't replace the collection instance (order.setLines(newList)). Hibernate throws "A collection with cascade=all-delete-orphan was no longer referenced". Clear it and add to it instead.
    • Moving a child to another parent with orphan removal can delete it.
    • It's per-entity deletes, so it's slow for huge collections.

Q9. What's the default fetch type for each kind of relationship?

Short answer: In JPA:

  • @ManyToOne and @OneToOne are EAGER by default;
  • @OneToMany and @ManyToMany are LAZY;
  • basic fields are eager (lazy loading of basics needs bytecode enhancement).

The best practice: set every association to FetchType.LAZY explicitly (@ManyToOne(fetch = LAZY)), and fetch what each use case needs through fetch joins, entity graphs or projections. Eager defaults on to-one associations are a common source of N+1 queries: every order loads its customer with a separate SELECT when you query a list.

Q10. What are the best practices for the owning versus inverse side of a relationship?

Short answer:

  • Put the owning side on the @ManyToOne (FK) side. It gives the most efficient SQL (a single insert with the FK).
  • The inverse @OneToMany(mappedBy) is optional. Add it for aggregate navigation and cascading.
  • Synchronise both sides in helper methods on the parent (addLine/removeLine), and don't expose setters that let callers break consistency.
  • Avoid unidirectional @OneToMany without @JoinColumn (it creates a join table, and extra statements).
  • For @OneToOne, prefer making the child the owner with @MapsId (a shared primary key), which avoids an extra FK column and lazy-loading problems on the inverse side.
  • Use Set for many-to-many, and List with @OrderColumn only if the order matters.
  • Define equals/hashCode using a business key, or a stable ID strategy (not generated IDs that are null before persist).

Q11. How do you map a one-to-one relationship with a shared primary key?

Short answer: The child's primary key is also the foreign key to the parent, which gives one ID for both rows. Use @MapsId on the child's @OneToOne:

@Entity
public class UserProfile {
    @Id private Long id;                                     // the same value as User.id
    @OneToOne(fetch = FetchType.LAZY) @MapsId
    @JoinColumn(name = "id")
    private User user;
    private String bio;
}
// Load a profile directly by the user ID: em.find(UserProfile.class, userId); no extra FK column, no join needed

The benefits:

  • no separate FK column or unique constraint;
  • the child can be looked up by the parent's ID;
  • and, importantly, it avoids the inverse-side one-to-one lazy-loading problem: without bytecode enhancement, a @OneToOne(mappedBy) on the parent can't be lazy, because Hibernate must query to know whether it's null. Often you don't map the parent side at all, and fetch the profile by ID when needed.

Follow-up questions this topic invites — and their answers

Q: Why do bidirectional @OneToMany updates sometimes produce an INSERT followed by an UPDATE? A: That's a unidirectional @OneToMany with @JoinColumn, or a missing owning-side assignment: Hibernate inserts the child, then updates its FK from the collection side. Mapping the relationship as bidirectional, with the @ManyToOne as owner (and a not-null FK), gives a single INSERT.

Q: List vs Set for @ManyToMany? A: A List without @OrderColumn is a "bag". Removing one element can make Hibernate delete every link row and re-insert the rest. A Set deletes exactly one row. Fetching two bags in one query also causes MultipleBagFetchException.

Q: How do you implement soft deletes with JPA? A: Use a deleted flag or deleted_at timestamp, with @SQLDelete (turning delete into an update) and @SQLRestriction (Hibernate 6.3+, which replaced @Where) to filter the deleted rows, or Hibernate's native @SoftDelete (6.4+). Remember the unique constraints (partial indexes) and cascades.

Q: Should entities use @Data from Lombok? A: No. The generated equals/hashCode touch lazy associations and mutable fields, and toString recurses and triggers loading. Use explicit getters, and carefully written equals/hashCode (a business key, or an ID with a null-safe strategy).

Previous

Spring Data JPA — Queries, Projections, Custom Repositories & Locking — Interview Questions

Next

JPQL vs Native Queries in Depth — Interview Questions

AI Tutor

Lesson: JPA Entity Mapping, Associations & Cascades — Interview Questions

Quick actions

AI responses can be inaccurate. Verify critical information.