What does @Modifying do, and when do you need clearAutomatically and flushAutomatically?
answer
- @Modifying → executeUpdate(), returns row count
- bulk DML bypasses persistence context → stale cache
- clearAutomatically = clear AFTER (reload fresh)
- flushAutomatically = flush BEFORE (don't lose changes)
- skips lifecycle callbacks + cascade; needs write tx
basics
~20 s@Modifying marks a @Query as an UPDATE or DELETE (or INSERT native) rather than a SELECT, so Spring calls executeUpdate() and returns the affected-row count. clearAutomatically clears the persistence context after the query so stale cached entities don't hide the change; flushAutomatically flushes pending changes before it runs.
solid answer
~40 sBy default `@Query` expects a SELECT. When your query is a bulk `UPDATE` or `DELETE` (or a native `INSERT`), you must add `@Modifying`, which tells Spring Data to invoke `Query.executeUpdate()` and return the number of affected rows (`int`/`void`). The critical subtlety is that bulk modifying queries execute **directly against the database, bypassing the persistence context (first-level cache)**. So entities already loaded in the current `EntityManager` are *not* updated and can be stale. `@Modifying(clearAutomatically = true)` clears the persistence context after the query, forcing subsequent reads to reload fresh state. `flushAutomatically = true` flushes pending managed-entity changes to the DB *before* the query runs, so those changes aren't lost or overwritten. Also: `@Modifying` requires a write transaction, and bulk operations skip lifecycle callbacks and cascade rules.
code
java · 22 linespublic interface UserRepository extends JpaRepository<User, Long> {
// flush pending changes -> run UPDATE -> clear context
@Modifying(clearAutomatically = true, flushAutomatically = true)
@Query("UPDATE User u SET u.active = false WHERE u.lastLogin < :cutoff")
int deactivateStale(@Param("cutoff") Instant cutoff);
@Modifying
@Query("DELETE FROM Token t WHERE t.expiresAt < :now")
int purgeExpired(@Param("now") Instant now);
}
@Service
class CleanupService {
private final UserRepository users;
CleanupService(UserRepository users) { this.users = users; }
@Transactional // write transaction required for @Modifying
void run(Instant cutoff) {
int affected = users.deactivateStale(cutoff);
}
}go deeper
Knows @Modifying is needed for UPDATE/DELETE queries and returns affected rows.
Explains the persistence-context bypass and that a write transaction is required.
Correctly reasons about flush-before/clear-after ordering and stale-cache scenarios, plus skipped callbacks/cascades.
Considers set-based vs entity-based mutation trade-offs, versioning/audit implications, and when bulk DML is the wrong tool.
## What @Modifying is for `@Modifying` (from `org.springframework.data.jpa.repository.Modifying`) is placed alongside `@Query` when the query **changes data** — a JPQL/`UPDATE`, `DELETE`, or a native `INSERT`. Without it, Spring Data assumes the query is a `SELECT` and calls `getResultList()`/`getSingleResult()`, which throws for a DML statement. With `@Modifying`, Spring calls `Query.executeUpdate()` instead. The method must return `void`, `int`, or `long` — the **affected-row count**. ```java @Modifying @Query("UPDATE User u SET u.active = false WHERE u.lastLogin < :cutoff") int deactivateStaleUsers(@Param("cutoff") Instant cutoff); ``` This issues a **single bulk SQL statement** — far more efficient than loading each entity and mutating it, because it avoids N round-trips and entity hydration. ## The core gotcha: the persistence context is bypassed A bulk `UPDATE`/`DELETE` runs **straight against the database**. It does **not** go through Hibernate's dirty-checking and does **not** synchronize the **persistence context** (the first-level cache — the set of managed entities the current `EntityManager` is tracking). Consequences within the same transaction/session: - Entities already loaded stay in their **old state** — the cache is now **stale**. - A `DELETE` bulk query removes rows in the DB, but a previously loaded entity for a deleted row still sits managed in the context. - Reading an entity after the bulk update may return the **cached, pre-update** version (Hibernate returns the managed instance without hitting the DB). ## clearAutomatically `@Modifying(clearAutomatically = true)` tells Spring to call `EntityManager.clear()` **after** the modifying query. This **detaches all managed entities**, so the next read reloads fresh rows from the database. Use it when the same transaction reads entities affected by the bulk change afterward. Caveat: clearing detaches **every** entity in the context — any un-flushed changes to *other* entities are **lost** unless they were already flushed. That's exactly why the flush option exists. ## flushAutomatically `@Modifying(flushAutomatically = true)` calls `EntityManager.flush()` **before** the query executes. This pushes pending in-memory changes (dirty managed entities) to the database first, so: - The bulk query sees those changes. - If you also `clearAutomatically`, you don't lose them (they've been persisted before the clear). Order of operations with both true: **flush pending changes → run the bulk UPDATE/DELETE → clear the persistence context.** ```java @Modifying(clearAutomatically = true, flushAutomatically = true) @Query("UPDATE Product p SET p.price = p.price * :factor WHERE p.category = :cat") int bumpPrices(@Param("factor") BigDecimal factor, @Param("cat") String cat); ``` ## Transaction requirement Modifying queries need a **read-write transaction**. Repository CRUD methods are transactional by default, but a custom `@Modifying` method is **not** automatically wrapped — if called without an ambient transaction you get `TransactionRequiredException`. Annotate the method or the calling service with `@Transactional`. (The precise `@Transactional` propagation/semantics belong to the transaction topic; here the point is simply that a write transaction must be present.) ## Other bulk-DML caveats - **Lifecycle callbacks are skipped.** `@PreUpdate`, `@PreRemove`, etc. do **not** fire for bulk operations. - **Cascading is not applied.** A bulk `DELETE` won't cascade to child entities the way `EntityManager.remove()` would; you must handle related rows yourself (or rely on DB-level `ON DELETE CASCADE`). - **Optimistic-lock `@Version` is not auto-incremented** by a bulk UPDATE unless you set it explicitly in the query. - **`deleteAllInBatch()`** on `JpaRepository` is a built-in bulk delete with the same cache-bypass characteristics — different from `deleteAll()`, which loads and removes entities one by one (firing callbacks). ## When to use / avoid Use `@Modifying` bulk queries for **large-scale, set-based** updates/deletes where per-entity semantics (callbacks, cascade, versioning) don't matter and performance does. Avoid them when you rely on lifecycle hooks, cascades, or need the persistence context to stay consistent without extra clearing. When mixing with loaded entities in the same transaction, enable `flushAutomatically` + `clearAutomatically` to stay safe.
- Why can a loaded entity appear unchanged after a bulk @Modifying UPDATE in the same transaction?The bulk query hits the database directly and bypasses the persistence context, so the already-managed entity keeps its stale cached state. clearAutomatically=true fixes it by clearing the context so the next read reloads.
- What is the difference between flushAutomatically and clearAutomatically?flushAutomatically flushes pending managed changes to the DB before the query so they aren't lost; clearAutomatically clears the persistence context after the query so subsequent reads see the modified rows. Together: flush -> execute -> clear.
- Do JPA lifecycle callbacks and cascades run for a bulk @Modifying delete?No. Bulk DML bypasses the entity lifecycle: @PreRemove/@PreUpdate don't fire, cascades aren't applied, and @Version isn't auto-bumped. You must handle related rows or use DB-level cascading.
saying these in an interview costs you the question
- Thinking a bulk UPDATE/DELETE updates already-loaded entities in the persistence context
- Believing clearAutomatically flushes changes (it clears/detaches, potentially losing unflushed changes)
- Assuming lifecycle callbacks or cascades fire for bulk DML
- Forgetting a write transaction is required (TransactionRequiredException)
- Returning an entity/List from a @Modifying method instead of void/int/long