How To Manage Transactions In Asynchronous Threads In Java (spring)

A practical guide to handling transactions correctly in async batch operations with database updates and remote service calls.
???? Introduction
In many enterprise systems, we often deal with batch operations executed in parallel threads, such as:
- Importing records and updating the database
- Sending notifications after saving data
- Interacting with external APIs after writing business-critical information to a local DB
In such cases, one key concern is:
How do we ensure each thread runs inside a proper transaction without affecting others?
This article shows how to design each thread to handle its own transaction independently, ensuring that failures in one thread do not impact the rest.
???? Problem Statement
Imagine this logic:
- Update database (local transaction)
- Call remote service (external dependency)
We want the transaction behavior to be:
- If any step fails, rollback only the current thread’s transaction
- Other threads should not be affected
- We must not accidentally share a single transaction context across threads
Incorrect Approach: Async Inside Transactional Method
@Service
public class BatchService {
@Transactional
public void processBatch(List<Item> items) {
items.forEach(item -> {
// Bad: Spawning async work inside a transactional method
CompletableFuture.runAsync(() -> {
updateDb(item); // Won't participate in the outer transaction
callRemoteService(item);
});
});
}
}
❌ This doesn't work as expected:
@Transactional
does not propagate into a new thread.
The DB update may execute outside of the transaction context!
✅ Correct Design: Transaction Per Thread
Use an explicitly scoped transactional method called inside each thread, not around it.
@Service
public class BatchService {
@Autowired
private ItemService itemService;
public void processBatch(List<Item> items) {
items.forEach(item -> {
CompletableFuture.runAsync(() -> {
try {
itemService.processOneItem(item); // Each thread has its own transaction
} catch (Exception e) {
// Log and continue; only this thread's TX rolls back
}
});
});
}
}
@Service
public class ItemService {
@Transactional
public void processOneItem(Item item) {
updateDb(item); // part of transaction
callRemoteService(item); // if this fails, TX rolls back
}
}
Now each thread runs
@Transactional
logic inprocessOneItem
, and any failure in DB or remote call will trigger rollback for only that item.
⚠️ Bonus Tips
- Don't use
@Transactional
on private methods (Spring AOP won't proxy them) - If you're using thread pools (
ExecutorService
), make sure each task independently calls a public service method annotated with@Transactional
- Log failures but don't let them crash the whole batch
Alternative: Using TransactionTemplate in the Same Class
In some cases, you may want to keep the logic in the same class (e.g., no split between BatchService
and ItemService
). Since @Transactional
won’t work properly in self-invocation
(Spring AOP won't trigger), you can use TransactionTemplate
programmatically:
@Service
public class BatchService {
@Autowired
private PlatformTransactionManager transactionManager;
public void processBatch(List<Item> items) {
TransactionTemplate template = new TransactionTemplate(transactionManager);
items.forEach(item -> {
CompletableFuture.runAsync(() -> {
try {
template.execute(status -> {
updateDb(item);
callRemoteService(item); // any exception rolls back
return null;
});
} catch (Exception e) {
// log and continue
}
});
});
}
private void updateDb(Item item) {
// update database
}
private void callRemoteService(Item item) {
// call external API
}
}
???? This approach avoids AOP limitations and gives you full control over each thread’s transaction boundary.
???? Final Thoughts
Proper transaction management in asynchronous batch operations can prevent data corruption, increase resilience, and make debugging easier.
If you're designing a system with concurrent operations and transactional safety, follow this "transaction-per-thread" model.
???? Related Reading
- Spring Docs: @Transactional and Thread Boundaries
- Java Concurrency in Practice – Effective parallel execution models
- How Spring AOP works under the hood
Popular Products
-
0-12 Multiplication Chart & Times Tab...
$9.99$4.78 -
-
-
-