HuntExams Academy logo
JavaAdvanced#spring

What is @Transactional and how does it work in Spring?

Answer

It marks a method or class as requiring a database transaction, managed via a proxy that begins a transaction before the method runs and commits/rolls back afterward based on success or exceptions, respecting the configured propagation and isolation settings.

Example

@Transactional
public void transferFunds(Long from, Long to, double amount) {
  debit(from, amount);
  credit(to, amount); // if this throws, debit() is rolled back too
}

Detailed explanation

Spring’s @Transactional declares transaction rules for a method or class, letting business code run without manually beginning, committing, or rolling back transactions. It is commonly placed on service methods that define a complete business operation. In transferFunds(), debit() and credit() should succeed together or leave no partial changes. This atomicity holds when both updates participate in the same transaction and use transactional resources. A class-level annotation supplies defaults, while method-level settings can override them.

In Spring’s default proxy-based mode, transaction infrastructure wraps an eligible bean in a proxy. When another bean calls a transactional method through that proxy, a transaction interceptor reads its configuration and delegates to a transaction manager, such as JpaTransactionManager or DataSourceTransactionManager. Depending on propagation, the manager starts a transaction or joins an existing one. For typical imperative JDBC/JPA applications, transaction resources are associated with the current thread, allowing participating data-access operations to share them. After execution, the interceptor completes a transaction it started; joining an existing transaction leaves final completion to the outer boundary.

By default, propagation is REQUIRED: join an existing transaction or create one. REQUIRES_NEW instead uses an independent transaction, suspending any existing transaction where supported. Isolation controls visibility of concurrent database changes; DEFAULT uses the underlying database’s default isolation. Isolation settings normally apply when creating a new transaction, not when joining one. A normal return usually leads to commit, while an escaping RuntimeException or Error triggers rollback by default. Checked exceptions do not trigger rollback unless configured, for example with rollbackFor = Exception.class. A rollback-only transaction cannot successfully commit even if execution returns normally.

Interviewers also expect practical limitations. Self-invocation, where one method calls another on the same instance, bypasses the proxy, so the inner annotation is not independently applied. Catching and swallowing an exception can prevent automatic rollback. The readOnly flag is generally an optimisation hint, not a guaranteed write prohibition. Transactions do not automatically cover remote API calls or work on newly started threads. For the funds example, use BigDecimal rather than double for monetary amounts, and explain that rollback depends on exception rules and shared transaction participation.

Key points to remember

  • @Transactional defines declarative transaction boundaries, typically at the service layer.
  • In default proxy mode, interception requires a call through the Spring-managed proxy.
  • REQUIRED joins or creates a transaction; REQUIRES_NEW uses an independent transaction.
  • RuntimeException and Error trigger rollback by default; checked exceptions require explicit rollback rules.
  • Isolation governs concurrent data visibility, while readOnly is generally a hint rather than enforcement.

Common mistakes candidates make

  • Assuming every exception causes rollback, including checked exceptions or exceptions caught inside the method.
  • Expecting self-invocation or manually constructed objects to receive proxy-based transaction interception.
  • Assuming a database rollback also reverses remote API calls, emails, or independently committed transactions.

Likely follow-up questions

What happens if a transactional method catches an exception?

If the exception is swallowed, the interceptor sees a normal return and ordinarily attempts to commit. However, a participating operation may already have marked the transaction rollback-only; otherwise, rethrow an exception matching the rollback rules or explicitly mark the transaction rollback-only when rollback is required.

How do REQUIRED and REQUIRES_NEW differ when the outer operation fails?

With REQUIRED, participating operations share one transaction, so rollback affects their shared work. With REQUIRES_NEW, the inner operation has an independent transaction, so its committed changes survive a later outer rollback.

How can you fix the self-invocation problem?

A common solution is to move the transactional operation into another Spring-managed service and call that injected service, ensuring the call crosses its proxy. AspectJ-based transaction weaving is another option, but it requires additional configuration and does not rely on proxy interception.

Interview tip: Explain the proxy-to-transaction-manager flow first, then use the transfer example to demonstrate default rollback rules and the self-invocation limitation.

Related Interview Questions

1
JavaBeginner#spring

What is the difference between application.properties and application.yml in Spring Boot?

Open
2
JavaBeginner#spring

What is the purpose of Spring Boot starters?

Open
3
JavaIntermediate#spring

What is the difference between @RequestParam, @PathVariable, and @RequestBody?

Open