I'm trying to chain Spring transaction events, but the second @TransactionalEventListener is not triggered when an event is published from within another AFTER_COMMIT listener. Here's a minimal example:
@Service
public class EventService {
@Autowired
private ApplicationEventPublisher eventPublisher;
@Transactional(rollbackFor = Exception.class)
public void startProcess() {
// This is called within a transaction
eventPublisher.publishEvent(new FirstEvent());
}
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void handleFirstEvent(FirstEvent event) {
System.out.println("FirstEvent handled successfully");
// Trying to publish another event here
eventPublisher.publishEvent(new SecondEvent());
}
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void handleSecondEvent(SecondEvent event) {
// This is NEVER called!
System.out.println("SecondEvent handled");
}
}
What happens:
startProcess()is called within a transaction- FirstEvent is published
- After transaction commits,
handleFirstEvent()is triggered ✅ - SecondEvent is published from within
handleFirstEvent() handleSecondEvent()is NEVER triggered ❌
What I've tried:
Annotation on handleFirstEvent() |
handleSecondEvent() triggered? |
|---|---|
| No annotation | ❌ NO |
@Transactional |
❌ NO |
@Transactional(propagation = Propagation.REQUIRES_NEW) |
✅ YES |
@Async("asyncExecutor") |
✅ YES |
Questions:
Why doesn't @Transactional on handleFirstEvent() create a proper transaction synchronization context when it's executed in the AFTER_COMMIT phase, causing the second @TransactionalEventListener to miss the event?
My logical expectation:
startProcess()runs in transaction T1FirstEventis published and bound to T1- T1 commits → triggers
handleFirstEvent()in AFTER_COMMIT phase handleFirstEvent()has@Transactional, and since T1 is already committed, Spring Framework should create a completely NEW transaction T2SecondEventis published within T2- T2 commits → should trigger
handleSecondEvent()in T2's AFTER_COMMIT phase handleSecondEvent()should execute
But in reality: Step 7 never happens. handleSecondEvent() is never called.
What's even more confusing:
When I change to @Transactional(propagation = Propagation.REQUIRES_NEW), it suddenly works! But why?
- Both
REQUIRED(default) andREQUIRES_NEWshould create a new transaction when no transaction exists - After T1 commits, there's no active transaction, so
REQUIREDshould behave likeREQUIRES_NEW