Transactional event listeners enable Spring events to be published and handled at specific transaction phases. This provides a clean way to decouple transactional operations from their side effects.
Marks a method as a listener for application events that should be handled at a specific transaction phase.
/**
* Marks a method as a listener for application events tied to transaction phases.
* The listener method is invoked according to the specified TransactionPhase.
*/
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@EventListener
public @interface TransactionalEventListener {
/**
* The transaction phase in which the event listener should be invoked.
* Default is AFTER_COMMIT.
*/
TransactionPhase phase() default TransactionPhase.AFTER_COMMIT;
/**
* Whether the event listener should be invoked if no transaction is running.
* Default is false (listener is not invoked without a transaction).
*/
boolean fallbackExecution() default false;
/**
* Alias for classes().
*/
@AliasFor(annotation = EventListener.class, attribute = "classes")
Class<?>[] value() default {};
/**
* The event classes this listener handles.
*/
@AliasFor(annotation = EventListener.class, attribute = "classes")
Class<?>[] classes() default {};
/**
* SpEL expression used for making the event handling conditional.
*/
@AliasFor(annotation = EventListener.class, attribute = "condition")
String condition() default "";
/**
* An optional identifier for the listener, defaulting to the fully-qualified
* signature of the declaring method.
*/
@AliasFor(annotation = EventListener.class, attribute = "id")
String id() default "";
}Usage Examples:
import org.springframework.transaction.event.TransactionalEventListener;
import org.springframework.transaction.event.TransactionPhase;
import org.springframework.context.event.EventListener;
@Service
public class OrderEventListener {
// Listen after successful commit (default)
@TransactionalEventListener
public void handleOrderCreated(OrderCreatedEvent event) {
emailService.sendOrderConfirmation(event.getOrder());
}
// Listen after commit with explicit phase
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void sendNotification(OrderCreatedEvent event) {
notificationService.notify(event.getOrder());
}
// Listen before commit (within transaction)
@TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
public void validateBeforeCommit(OrderCreatedEvent event) {
validator.finalValidation(event.getOrder());
}
// Listen after rollback
@TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK)
public void handleRollback(OrderCreatedEvent event) {
logger.warn("Order creation rolled back: {}", event.getOrder().getId());
cleanupService.cleanup(event.getOrder());
}
// Listen after completion (commit or rollback)
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMPLETION)
public void logCompletion(OrderCreatedEvent event) {
auditLog.record("Order transaction completed: " + event.getOrder().getId());
}
// With fallback execution (also runs without transaction)
@TransactionalEventListener(
phase = TransactionPhase.AFTER_COMMIT,
fallbackExecution = true
)
public void handleWithFallback(OrderCreatedEvent event) {
// Runs after commit if in transaction, otherwise runs immediately
cacheService.invalidate(event.getOrder().getId());
}
// With conditional execution using SpEL
@TransactionalEventListener(
phase = TransactionPhase.AFTER_COMMIT,
condition = "#event.order.total > 1000"
)
public void handleHighValueOrder(OrderCreatedEvent event) {
vipService.processHighValueOrder(event.getOrder());
}
// Multiple event types
@TransactionalEventListener(
phase = TransactionPhase.AFTER_COMMIT,
classes = {OrderCreatedEvent.class, OrderUpdatedEvent.class}
)
public void handleOrderEvents(ApplicationEvent event) {
if (event instanceof OrderCreatedEvent) {
handleCreated((OrderCreatedEvent) event);
} else if (event instanceof OrderUpdatedEvent) {
handleUpdated((OrderUpdatedEvent) event);
}
}
}Defines the phase in which a transactional event listener should be invoked.
/**
* Enumeration representing the phase in which transactional event listeners are invoked.
*/
public enum TransactionPhase {
/**
* Handle the event before transaction commit.
* Listener is invoked within the transaction boundaries.
*/
BEFORE_COMMIT,
/**
* Handle the event after the transaction committed successfully.
* This is the default phase. Listener is invoked after successful commit.
*/
AFTER_COMMIT,
/**
* Handle the event if the transaction rolled back.
* Listener is invoked after rollback.
*/
AFTER_ROLLBACK,
/**
* Handle the event after the transaction completed (committed or rolled back).
* Listener is invoked after transaction completion, regardless of outcome.
*/
AFTER_COMPLETION
}Usage Pattern:
@Service
public class TransactionPhaseExamples {
// BEFORE_COMMIT: Final validation within transaction
@TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
public void beforeCommit(DataModifiedEvent event) {
// Still within transaction, can query database
// Exception here causes rollback
performFinalValidation(event.getData());
}
// AFTER_COMMIT: External system integration
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void afterCommit(DataModifiedEvent event) {
// Transaction committed, safe to call external systems
externalApiClient.notifyChange(event.getData());
searchIndexService.update(event.getData());
}
// AFTER_ROLLBACK: Cleanup and compensation
@TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK)
public void afterRollback(DataModifiedEvent event) {
// Transaction rolled back, perform cleanup
logger.error("Transaction failed for: {}", event.getData().getId());
compensationService.compensate(event.getData());
}
// AFTER_COMPLETION: Always executed
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMPLETION)
public void afterCompletion(DataModifiedEvent event) {
// Runs after commit OR rollback
metricsService.recordTransactionAttempt();
resourceCleanup.cleanup(event.getData());
}
}Events are published using Spring's ApplicationEventPublisher:
@Service
public class OrderService {
private final ApplicationEventPublisher eventPublisher;
private final OrderRepository orderRepository;
public OrderService(ApplicationEventPublisher eventPublisher,
OrderRepository orderRepository) {
this.eventPublisher = eventPublisher;
this.orderRepository = orderRepository;
}
@Transactional
public Order createOrder(OrderRequest request) {
Order order = new Order(request);
orderRepository.save(order);
// Publish event within transaction
eventPublisher.publishEvent(new OrderCreatedEvent(this, order));
return order;
}
@Transactional
public void cancelOrder(Long orderId) {
Order order = orderRepository.findById(orderId);
order.setStatus(OrderStatus.CANCELLED);
orderRepository.save(order);
eventPublisher.publishEvent(new OrderCancelledEvent(this, order));
}
}// Simple event class
public class OrderCreatedEvent extends ApplicationEvent {
private final Order order;
public OrderCreatedEvent(Object source, Order order) {
super(source);
this.order = order;
}
public Order getOrder() {
return order;
}
}
// Or without extending ApplicationEvent (Spring 4.2+)
public class OrderCancelledEvent {
private final Order order;
public OrderCancelledEvent(Order order) {
this.order = order;
}
public Order getOrder() {
return order;
}
}@Service
public class UserService {
private final ApplicationEventPublisher eventPublisher;
@Transactional
public User registerUser(UserRegistration registration) {
// Core business logic
User user = new User(registration);
userRepository.save(user);
// Publish event - listeners handle side effects
eventPublisher.publishEvent(new UserRegisteredEvent(user));
return user;
}
}
@Component
public class UserRegistrationHandlers {
// Send welcome email after commit
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void sendWelcomeEmail(UserRegisteredEvent event) {
emailService.sendWelcomeEmail(event.getUser());
}
// Update search index after commit
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void updateSearchIndex(UserRegisteredEvent event) {
searchService.indexUser(event.getUser());
}
// Notify analytics after commit
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void notifyAnalytics(UserRegisteredEvent event) {
analyticsService.trackUserRegistration(event.getUser());
}
}@Component
public class OrderEventHandlers {
// Only handle high-value orders
@TransactionalEventListener(
phase = TransactionPhase.AFTER_COMMIT,
condition = "#event.order.total > 1000"
)
public void handleHighValueOrder(OrderCreatedEvent event) {
vipService.notifyHighValueOrder(event.getOrder());
}
// Only handle orders with specific status
@TransactionalEventListener(
phase = TransactionPhase.AFTER_COMMIT,
condition = "#event.order.status.name() == 'PREMIUM'"
)
public void handlePremiumOrder(OrderCreatedEvent event) {
premiumProcessingService.process(event.getOrder());
}
// Complex condition
@TransactionalEventListener(
phase = TransactionPhase.AFTER_COMMIT,
condition = "#event.order.customer.vip and #event.order.total > 500"
)
public void handleVipOrder(OrderCreatedEvent event) {
vipOrderService.expedite(event.getOrder());
}
}@Component
public class PaymentEventHandlers {
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void processPayment(OrderCreatedEvent event) {
try {
paymentGateway.charge(event.getOrder());
} catch (PaymentException e) {
// Payment failed after order committed
// Publish compensation event
eventPublisher.publishEvent(
new PaymentFailedEvent(event.getOrder(), e)
);
}
}
@TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK)
public void handleOrderRollback(OrderCreatedEvent event) {
// Order creation rolled back, cleanup any pre-allocated resources
resourceCleanupService.cleanup(event.getOrder());
}
}@Component
public class AsyncEventHandlers {
// Process event asynchronously after commit
@Async
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void sendEmailAsync(OrderCreatedEvent event) {
// Runs in separate thread after transaction commits
emailService.sendOrderConfirmation(event.getOrder());
}
// Enable async with @EnableAsync in configuration
}
@Configuration
@EnableAsync
public class AsyncConfig {
// Async configuration
}@Component
public class OrderedEventHandlers {
// Execute first (lower order value = higher priority)
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
@Order(1)
public void firstHandler(OrderCreatedEvent event) {
// Executed first
criticalService.process(event.getOrder());
}
// Execute second
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
@Order(2)
public void secondHandler(OrderCreatedEvent event) {
// Executed second
lessCriticalService.process(event.getOrder());
}
}@TransactionalEventListener extends Spring's @EventListener with transaction awarenessAFTER_COMMIT - most common use caseBEFORE_COMMIT listeners execute within transaction boundariesAFTER_COMMIT listeners only execute if transaction commits successfullyAFTER_ROLLBACK listeners only execute if transaction rolls backAFTER_COMPLETION listeners execute regardless of commit/rollbackfallbackExecution = true to execute listeners even without a transactionBEFORE_COMMIT listeners cause transaction rollbackcondition attribute for conditional execution@Async for asynchronous processing@Order is usedPlatformTransactionManager bean