Which cascade operations can be configured on a JPA association, what does each one propagate, and why does the specification make cascading opt-in rather than the default?
answer
- PERSIST MERGE REMOVE REFRESH DETACH = ALL
- one cascade per EntityManager method
- transitive and directional
- ALL ≠ orphanRemoval; flush is never cascaded
- Hibernate extras: SAVE_UPDATE, REPLICATE, LOCK
basics
~20 sPERSIST, MERGE, REMOVE, REFRESH, DETACH, and ALL (all five). Each forwards the matching EntityManager call along the association. Cascading is opt-in because propagation is only correct where the parent truly owns the target — REMOVE especially would destroy shared data.
solid answer
~50 sThe standard set is `PERSIST`, `MERGE`, `REMOVE`, `REFRESH`, `DETACH`, and `ALL` as the union. Each mirrors the identically named `EntityManager` method: cascading `MERGE` means `merge(parent)` merges the children, cascading `DETACH` means `detach(parent)` evicts them from the persistence context, and so on. Propagation is transitive — it follows every cascading edge onward. Hibernate adds a few non-standard ones through `org.hibernate.annotations.Cascade`, mainly `SAVE_UPDATE`, `REPLICATE` and `LOCK`, dating from the pre-JPA Session API. Cascading is opt-in because the provider cannot infer ownership from a mapping. The same shape — a `@ManyToMany` between `Post` and `Tag`, say — can mean composition or plain reference. Making `REMOVE` implicit would delete shared rows; making `REFRESH`/`DETACH` implicit would silently walk and load large graphs. Two things `ALL` does **not** include: `orphanRemoval` (a separate flag with different semantics) and JPA `flush`, which always covers everything managed.
code
java · 8 lines@Entity
public class Order {
@OneToMany(mappedBy = "order", cascade = {CascadeType.PERSIST, CascadeType.MERGE})
private List<OrderLine> lines = new ArrayList<>();
@ManyToOne // no cascade: the customer outlives this order
private Customer customer;
}go deeper
List the six constants and state that each forwards the matching EntityManager operation along the association.
Add transitivity, directionality, and the distinction between ALL and orphanRemoval; explain why REMOVE is opt-in.
Emphasise cost — graph walking, per-row DELETEs, lazy initialisation triggered by cascade — and when you deliberately skip cascade for a bulk operation.
Present cascade as the encoding of aggregate ownership in the mapping layer and discuss the review rule you would enforce across a large model.
## The standard cascade types `jakarta.persistence.CascadeType` defines six constants. Five map one-to-one onto `EntityManager` methods; the sixth is a union. | Type | Propagates | Typical use | |---|---|---| | `PERSIST` | `em.persist(x)` to associated instances, including at flush for newly reachable objects | saving an aggregate in one call | | `MERGE` | `em.merge(x)` to associated instances; transient targets become new managed entities | reattaching a detached graph edited outside a transaction | | `REMOVE` | `em.remove(x)` to associated instances | deleting an aggregate root together with its owned children | | `REFRESH` | `em.refresh(x)`, re-reading state from the database and discarding in-memory changes | resetting a whole graph from the DB | | `DETACH` | `em.detach(x)`, evicting instances from the persistence context | handing a graph out of a transaction | | `ALL` | all five above | genuine composition only | Cascading is **transitive**: `persist(order)` with cascade to `lines`, and `lines` cascading to `discount`, reaches the discount too. It is also **directional** — it applies to the association you declare it on, in the direction that association points. Putting cascade on the child's `@ManyToOne` means operations flow child → parent, which is almost never what you want for `REMOVE`. ## Which ones you actually configure In practice the safe workhorse pair is `PERSIST, MERGE` on an aggregate's owned collections. `REMOVE` is added deliberately when the child cannot exist without the parent. `REFRESH` and `DETACH` are rarely set explicitly; you mostly acquire them by writing `ALL`, which is one reason `ALL` deserves a second look. `REFRESH` cascading over a large graph produces a wide read; `DETACH` cascading can pull entities out of the context that other code still expects to be managed. ## Hibernate's extra types `org.hibernate.annotations.Cascade` adds legacy modes tied to the native `Session` API: - `SAVE_UPDATE` — propagates `Session.saveOrUpdate`, a pre-JPA operation with no JPA equivalent. - `REPLICATE` — propagates `Session.replicate`, used for cross-database copying. - `LOCK` — propagates reattachment/lock of a detached instance. Modern code sticks to the JPA set unless it is already using the `Session` API directly. Hibernate also exposes `DELETE_ORPHAN` there, superseded by the standard `orphanRemoval = true` attribute. ## Why not cascade by default Three independent reasons: **1. Ownership is not derivable from the mapping.** `@ManyToOne Customer customer` on an `Order` and `@ManyToOne Order order` on an `OrderLine` are structurally identical, yet one target is shared reference data and the other is an owner. Only the modeller knows. **2. `REMOVE` is destructive and irreversible.** An implicit `REMOVE` on a `@ManyToMany` would delete every `Tag` a deleted `Post` used, including tags other posts still reference — either wiping shared data or blowing up on a FK constraint from the join table. **3. Cost.** Cascading walks the object graph. `REMOVE` in particular cannot be done as a single bulk `DELETE`: Hibernate loads each child (initialising lazy collections to find them), enrols it in the persistence context, and emits one `DELETE` per row so that lifecycle callbacks and second-level cache eviction happen correctly. Making that the default would turn every `remove()` into an unbounded operation. ## Two things `ALL` does not give you - **`orphanRemoval`.** It is a separate attribute, not a cascade type. `ALL` deletes children when the *parent* is removed; `orphanRemoval = true` also deletes a child when it is merely disassociated from the parent's collection. - **Flush.** Flushing is not a cascade type — the persistence context flushes every managed entity it holds, whatever the associations say. ## Interview framing A good answer names the six, explains propagation as "the same `EntityManager` call, forwarded along this edge", notes transitivity and directionality, and lands on the judgement: cascade encodes ownership, so it belongs on composition edges only, and `ALL` is a claim that the target has no life of its own.
- Does CascadeType.ALL include orphanRemoval?No. `ALL` is only the union of the five lifecycle operations. `orphanRemoval = true` is a separate attribute on `@OneToOne`/`@OneToMany` with different semantics: it deletes a child when it is removed from the parent's collection or its reference is nulled, even though the parent itself is untouched. `ALL` only reacts to `em.remove(parent)`.
- What does cascading REFRESH or DETACH actually do, and why is it risky to switch on casually?Cascading `REFRESH` re-reads the associated rows from the database and discards their in-memory changes, so unsaved edits deeper in the graph vanish. Cascading `DETACH` evicts the associated entities from the persistence context, so later access to their lazy state fails and later modifications are no longer tracked. Both walk the graph, so on a large aggregate they can turn a cheap single-entity call into a wide read or a broad eviction.
saying these in an interview costs you the question
- Saying CascadeType.ALL also enables orphan removal
- Treating cascade as bidirectional — it only follows the association it is declared on
- Claiming cascade REMOVE issues one bulk DELETE for the whole collection
- Describing flush as something you configure via cascade
- Putting cascade on the child's @ManyToOne and expecting parent-to-child behaviour