Definition
- CascadeType.PERSIST – persisting the parent also persists new children reachable through the association.
- CascadeType.MERGE – merging a detached parent also merges children, copying detached state into managed instances.
- CascadeType.REMOVE – removing the parent also removes its children; fires ONLY when the parent is deleted, not when a child is removed from the collection.
- CascadeType.DETACH – detaching the parent from the persistence context also detaches its children.
- CascadeType.REFRESH – refreshing the parent re-reads children from the database, discarding in-memory changes.
- CascadeType.ALL – shorthand for all five types; use for true aggregates where children have no life outside the parent.
- orphanRemoval – deletes a child when it is disassociated from the parent's collection, even while the parent lives on.
A customer (parent) at National Bank of India has orders (children). Cascade is the instruction: whatever I do to the customer, do the same to their orders. Save the customer and PERSIST cascades the new orders. Update a detached customer and MERGE cascades the updates. Close the customer account and REMOVE cascades to delete their orders. Orphan removal is a different, sharper rule: if I tear one order slip out of the customer's folder, shred that one slip, even though the customer still exists.
Understanding the Concepts
What is CascadeType.PERSIST?
PERSIST means when you persist a new parent (em.persist or save), Hibernate also persists any new children reachable through the cascaded association. Without it, you would have to persist each child yourself.
package com.bank.model;
import jakarta.persistence.*;
import java.util.ArrayList;
import java.util.List;
@Entity
public class User {
@Id
private String userId;
@OneToMany(mappedBy = "user", cascade = CascadeType.PERSIST)
private List<Order> orders = new ArrayList<>();
}
package com.bank.service;
import com.bank.model.User;
import com.bank.model.Order;
import com.bank.repository.UserRepository;
import java.math.BigDecimal;
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public void createUserWithOrders() {
User user = new User("USR001", "Rajesh");
Order order1 = new Order("ORD001", new BigDecimal("50000.00"));
Order order2 = new Order("ORD002", new BigDecimal("25000.00"));
user.addOrder(order1);
user.addOrder(order2);
userRepository.save(user); // CASCADE PERSIST: saves user + both orders
}
}
Generated SQL:
INSERT INTO users (user_id, name) VALUES ('USR001', 'Rajesh')
INSERT INTO orders (order_id, user_id, amount) VALUES ('ORD001', 'USR001', 50000.00)
INSERT INTO orders (order_id, user_id, amount) VALUES ('ORD002', 'USR001', 25000.00)
What is CascadeType.MERGE?
MERGE means when you merge a detached parent (em.merge), Hibernate also merges the children, copying their detached state into managed instances. This is what you want when an entity graph was built outside a transaction and you reattach the whole thing.
@OneToMany(mappedBy = "user", cascade = CascadeType.MERGE)
private List<Order> orders;
// Detached user from API request with modified order amounts
// em.merge(detachedUser) -> UPDATEs user AND its orders from the detached graph
MERGE flow:
1. Receive detached User with modified Order amounts from API
2. em.merge(detachedUser)
3. Hibernate finds managed User, copies detached state
4. CASCADE MERGE: also finds managed Orders, copies detached state
5. UPDATE users SET ... WHERE user_id = 'USR001'
6. UPDATE orders SET amount = 60000 WHERE order_id = 'ORD001'
What is CascadeType.REMOVE?
REMOVE means when you remove the parent (em.remove), Hibernate also removes its children. The important limitation: REMOVE only fires when the PARENT itself is deleted. It does NOT fire when you merely remove a child from the parent's collection.
@OneToMany(mappedBy = "user", cascade = CascadeType.REMOVE)
private List<Order> orders;
// em.remove(user) -> DELETE orders WHERE user_id = 'USR001', then DELETE users WHERE ...
// user.getOrders().remove(order) -> NOTHING deleted! (need orphanRemoval for that)
CASCADE REMOVE:
- em.remove(user) -> DELETEs orders, then DELETEs user
- user.getOrders().remove(order) -> NO delete (REMOVE cascade not triggered)
orphanRemoval:
- user.getOrders().remove(order) -> DELETEs that order (orphan detected)
What is CascadeType.ALL?
ALL is shorthand for PERSIST + MERGE + REMOVE + DETACH + REFRESH. Use it for a true aggregate where the children's entire lifecycle is owned by the parent.
package com.bank.model;
import jakarta.persistence.*;
import java.util.ArrayList;
import java.util.List;
@Entity
public class User {
@Id
private String userId;
private String name;
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Order> orders = new ArrayList<>();
// ALL covers the complete lifecycle:
// PERSIST: save user -> save orders
// MERGE: merge user -> merge orders
// REMOVE: delete user -> delete orders
// DETACH: detach user -> detach orders
// REFRESH: refresh user -> refresh orders
// + orphanRemoval: remove order from collection -> delete order
}
How does cascade REMOVE differ from orphanRemoval?
This is the distinction interviewers probe most. They sound similar but fire on different triggers.
| Trigger | CascadeType.REMOVE | orphanRemoval=true |
|---|---|---|
| em.remove(parent) | Deletes children | Deletes children |
| parent.getChildren().remove(child) | Nothing | Deletes that child |
| When it fires | Parent deletion only | Child disassociation |
package com.bank.service;
import com.bank.model.User;
import com.bank.model.Order;
import com.bank.repository.UserRepository;
public class CascadeDemo {
private final UserRepository userRepository;
public CascadeDemo(UserRepository userRepository) {
this.userRepository = userRepository;
}
public void demonstrateDifference() {
User user = userRepository.findWithOrders("USR001");
// CASCADE REMOVE: only fires when parent is deleted
userRepository.delete(user);
// SQL: DELETE FROM orders WHERE user_id = 'USR001'
// SQL: DELETE FROM users WHERE user_id = 'USR001'
// ORPHAN REMOVAL: fires when child is removed from collection
user.getOrders().removeIf(o -> o.getOrderId().equals("ORD001"));
// SQL: DELETE FROM orders WHERE order_id = 'ORD001'
// User still exists!
}
}
What are common mistakes with cascade?
The most common mistake is applying CascadeType.ALL or REMOVE to a relationship pointing at shared reference data. Deleting one parent could delete data another parent still needs. Cascade should only be used on genuinely owned children.
// WRONG: cascade ALL on shared reference data
package com.bank.model;
import jakarta.persistence.*;
import java.util.List;
@Entity
public class User {
@Id
private String userId;
@ManyToMany(cascade = CascadeType.ALL) // DANGEROUS!
private List<Currency> preferredCurrencies;
// Deleting one user would DELETE shared Currency records!
}
// CORRECT: no cascade on shared data, cascade only on owned children
package com.bank.model;
import jakarta.persistence.*;
import java.util.List;
@Entity
public class User {
@Id
private String userId;
@ManyToMany // no cascade: currencies are shared reference data
private List<Currency> preferredCurrencies;
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Order> orders; // owned children: cascade is correct
}
What about DETACH and REFRESH?
DETACH cascades em.detach(parent) to children, so changes to children stop being tracked. REFRESH cascades em.refresh(parent) to children, re-reading them from the database and discarding in-memory changes. Both are less commonly used but important for graph-level operations.
package com.bank.service;
import jakarta.persistence.EntityManager;
import com.bank.model.User;
public class RefreshDemo {
private final EntityManager em;
public RefreshDemo(EntityManager em) {
this.em = em;
}
public void refreshUserGraph(String userId) {
User user = em.find(User.class, userId);
// Some code modified user and orders in memory...
em.refresh(user); // CASCADE REFRESH: re-reads user AND orders from DB
// All in-memory changes discarded for the entire graph
}
}
Does cascade apply to bulk JPQL operations?
No. Cascade is a JPA/ORM concern that applies to EntityManager lifecycle operations (persist, merge, remove). Bulk JPQL DELETE statements bypass the persistence context entirely and do NOT trigger cascades. If you need cascade-like behaviour in bulk, use the database-level ON DELETE CASCADE foreign key.
// This JPQL DELETE does NOT trigger cascade!
@Modifying
@Query("DELETE FROM User u WHERE u.status = 'CLOSED'")
int deleteClosedUsers();
// Orders are NOT deleted because cascade is bypassed!
// Fix: add ON DELETE CASCADE at the database level
// ALTER TABLE orders ADD CONSTRAINT fk_order_user FOREIGN KEY (user_id)
// REFERENCES users(user_id) ON DELETE CASCADE;
What should a senior engineer know?
A senior engineer cascades only to genuinely owned children, never to shared reference data. They know that CascadeType.ALL includes REMOVE, so they apply it deliberately. They know cascade is independent of database-level ON DELETE CASCADE and keep the two aligned. They use orphanRemoval for the complete aggregate lifecycle.
// Senior pattern: complete aggregate with ALL + orphanRemoval
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Order> orders;
// persist user -> persist orders
// merge user -> merge orders
// delete user -> delete orders
// remove order from collection -> delete that order
// Complete lifecycle management in one annotation
Visual Overview
Cascade Types
============================================
em.persist(parent) -> CascadeType.PERSIST -> persist children
em.merge(parent) -> CascadeType.MERGE -> merge children
em.remove(parent) -> CascadeType.REMOVE -> remove children
em.detach(parent) -> CascadeType.DETACH -> detach children
em.refresh(parent) -> CascadeType.REFRESH -> refresh children
CascadeType.ALL = all five combined
CASCADE REMOVE vs orphanRemoval
============================================
em.remove(user):
CASCADE REMOVE: DELETE orders, DELETE user (both fire)
orphanRemoval: DELETE orders, DELETE user (both fire)
user.getOrders().remove(order):
CASCADE REMOVE: nothing happens (not triggered)
orphanRemoval: DELETE that order (fires!)
Key difference:
CASCADE REMOVE = parent deletion cascades
orphanRemoval = child disassociation deletes
Key Takeaway
Cascade controls whether an operation on a parent entity automatically propagates to its associated children. PERSIST saves new children when the parent is saved. MERGE copies detached children state when the parent is merged. REMOVE deletes children when the parent is deleted. DETACH and REFRESH propagate detaching and re-reading across the graph. ALL is shorthand for all five, and Rajesh uses it for true aggregates at National Bank of India where orders have no life outside their user.
The distinction interviewers probe is cascade REMOVE versus orphan removal. Cascade REMOVE deletes children only when the parent itself is deleted. Orphan removal deletes a child the moment it is removed from the parent's collection, even while the parent continues to exist. Both mechanisms together give the complete aggregate lifecycle: creating, updating, deleting the whole user graph, and cancelling individual orders.
The senior cautions Pankaj gives are that cascade follows the object graph, so applying REMOVE or ALL to shared reference data is dangerous. He also remembers that cascade applies to EntityManager lifecycle operations and is completely bypassed by bulk JPQL DELETE statements, and that JPA cascade is a separate mechanism from a database-level ON DELETE CASCADE foreign key.
Remember one sentence: cascade propagates parent lifecycle operations to children, orphanRemoval deletes children removed from the collection, and both should only be applied to genuinely owned children.
---