skip to content

When would you choose NOT_SUPPORTED for a reporting call, and what design trade-offs and pitfalls must you weigh?

level: principalimportance: nice to knowfreq 18%

answer

  1. Long report mid-transaction, don't pin caller's connection/locks
  2. Pool doubling: outer idle + inner active
  3. No read-your-writes across the boundary
  4. Self-invocation silently defeats it
  5. Often better: shrink the tx or use a replica

basics

~10 s

Use NOT_SUPPORTED for a long read-only/reporting query called from within a transaction, so you don't hold the caller's transaction, locks, and connection open. Weigh connection-pool doubling, lost read-your-writes visibility, and proxy self-invocation.

solid answer

~50 s

NOT_SUPPORTED fits a slow, read-only reporting query that you want executed outside an enclosing transaction — for example an aggregation invoked mid-transaction that would otherwise pin the caller's connection and hold locks for seconds. Suspending releases the caller from that burden while the read runs on its own connection. The trade-offs: during the suspended window the thread holds two connections (idle outer + active inner), so size the pool accordingly or risk exhaustion; the reporting read won't see the caller's uncommitted writes, breaking read-your-writes expectations; and it only helps if the call actually goes through the Spring proxy (self-invocation defeats it). Often a cleaner design is to move the report out of the transactional path entirely — run it before the transaction, or in a separate service/thread — rather than relying on suspension. NOT_SUPPORTED is the right tool only when the report genuinely must run mid-transaction but non-transactionally.

go deeper

for a junior

Know the use case phrase: long read-only/reporting call outside the caller's transaction.

for a middle

Add the connection and visibility caveats to the use-case answer.

for a senior

Compare against restructuring the transaction and self-invocation pitfalls.

for a principal

Frame the decision against pool sizing, read replicas, async offload, and failure semantics; treat NOT_SUPPORTED as a last resort.

## The problem NOT_SUPPORTED solves Imagine a transactional workflow (`@Transactional` = `REQUIRED`) that, partway through, needs a heavy read — a reporting rollup, an analytics aggregation, an external-facing snapshot. If that read runs inside the outer transaction it: - keeps the outer transaction's connection pinned and busy for the duration, - keeps any locks/undo the outer transaction acquired held longer, - extends the outer transaction's lifetime, increasing lock-contention and deadlock windows. Marking the read `@Transactional(propagation = NOT_SUPPORTED, readOnly = true)` suspends the outer transaction, runs the read on a separate connection, and resumes — so the read's duration no longer prolongs the transactional critical section in the same way. ## Trade-offs to weigh **1. Connection-pool pressure.** While suspended, the outer connection remains checked out (idle but open) and the NOT_SUPPORTED body borrows a *second* connection. Every such thread doubles its connection footprint for the window. On a constrained HikariCP pool with many concurrent workflows, this is a real exhaustion risk. Model it: `peak_connections ≈ concurrent_threads × 2` during the window. **2. Lost read-your-writes.** The report runs outside the caller's transaction, so under READ_COMMITTED it sees only committed data — not the caller's in-flight writes. If the report is supposed to reflect changes the same transaction just made, NOT_SUPPORTED is wrong. **3. Non-atomicity / error semantics.** Anything the report writes (rare, but possible) commits independently and won't roll back with the outer transaction; an exception inside doesn't automatically roll the outer back. Reason carefully about failure paths. **4. Proxy self-invocation.** NOT_SUPPORTED only takes effect when the call crosses the Spring AOP proxy. A `this.buildReport()` call inside the same bean is not intercepted, so no suspension occurs — the read silently runs *inside* the outer transaction, defeating the intent. Put the method on a different bean (or use `AopContext.currentProxy()`), and cover it with a test. **5. Manager capability.** Requires a suspension-capable `PlatformTransactionManager` (`DataSourceTransactionManager`, `JpaTransactionManager`, `JtaTransactionManager`). Otherwise `TransactionSuspensionNotSupportedException`. ## Alternatives that are often better - **Restructure the boundary:** compute the report *before* opening the transaction, or *after* committing it, so no suspension is needed at all. Smaller transactions beat clever propagation. - **Separate read replica / read-only datasource:** route reporting to a replica via a dedicated `DataSource`/transaction manager, decoupling it from the write path entirely. - **Asynchronous offload:** run the report on a separate thread/`@Async` with its own transaction context if it doesn't need to block the workflow. ## Decision guidance Reach for NOT_SUPPORTED only when: (a) the read genuinely must happen *mid-transaction* in the same call flow, (b) it must **not** be part of the transaction (for duration or lock reasons), and (c) it does **not** need the caller's uncommitted state. If any of those fails, prefer restructuring or a replica. Document the choice, and load-test the pool.

  • A teammate puts NOT_SUPPORTED on a same-class method and it isn't suspending. Why?
    Self-invocation bypasses the Spring proxy, so the propagation metadata is never applied. The method must be called through a proxied bean (a different bean, or the injected self-proxy) for NOT_SUPPORTED to take effect.
  • How would you validate that NOT_SUPPORTED won't exhaust your connection pool under load?
    Model peak usage as roughly concurrent workflow threads times two during the suspended window, compare against the HikariCP maximumPoolSize, and load-test the worst case. If it's tight, prefer restructuring the transaction or routing the report to a read replica.
  • Would a read replica be a better fit than NOT_SUPPORTED here?
    Often yes — routing reporting reads to a dedicated read-only DataSource/replica decouples them from the write transaction entirely, avoids the double-connection window, and scales reads independently. NOT_SUPPORTED is only preferable when the read must stay in the same call flow on the primary.

saying these in an interview costs you the question

  • Reaching for NOT_SUPPORTED when simply shrinking the transaction would do
  • Ignoring the doubled connection usage during suspension
  • Expecting the report to see the caller's uncommitted writes
  • Placing the annotated method as a self-invoked same-class call

context