Fixing Spring Boot 3 Deadlock: Reproduce, Analyze, and Resolve Custom Event Publishing Issues
This article demonstrates how a custom event published from a constructor in Spring Boot 3.2.5 can cause a deadlock, explains the underlying lock contention, and provides two solutions: using SmartInitializingSingleton to defer publishing and upgrading to Spring 6.2's multithreaded bean initialization.
Environment: Spring Boot 3.2.5.
1. Deadlock Reproduction
1.1 Custom Event Definition
public class PackApplicationEvent extends ApplicationEvent {
private String message;
public PackApplicationEvent(String message, Object source) {
super(source);
this.message = message;
}
public String getMessage() {
return message;
}
}Custom event carries a message and source data.
1.2 Custom Event Listener
@Component
public class PackApplicationListener implements ApplicationListener<PackApplicationEvent> {
@Override
public void onApplicationEvent(PackApplicationEvent event) {
System.out.printf("Received event message: %s, data: %s%n",
event.getMessage(), event.getSource().toString());
}
}The listener simply prints the received information.
1.3 Publishing the Event
@Component
public class EventProcessor {
public EventProcessor(ApplicationEventPublisher eventPublisher) {
Thread t = new Thread(() -> {
eventPublisher.publishEvent(
new PackApplicationEvent("Custom Event", EventProcessor.this));
});
t.start();
try {
System.out.println("Thread started, waiting for completion...");
t.join();
} catch (InterruptedException e) {
System.err.printf("Thread interrupted: %s, error: %s%n",
Thread.currentThread().getName(), e.getMessage());
}
}
}This bean creates a new thread in its constructor, publishes the event, and waits for the thread to finish.
Running the program prints the listener output, but the application then hangs. A jstack dump shows that the main thread holds the DefaultSingletonBeanRegistry.singletonObjects lock while the new thread tries to acquire the same lock to create the listener bean, resulting in a deadlock.
2. Solutions
2.1 Use SmartInitializingSingleton
Do not publish events in a constructor. Implement SmartInitializingSingleton so that event publishing occurs after all singleton beans are created.
@Component
public class EventProcessor implements SmartInitializingSingleton {
private final ApplicationEventPublisher eventPublisher;
public EventProcessor(ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}
@Override
public void afterSingletonsInstantiated() {
Thread t = new Thread(() -> {
eventPublisher.publishEvent(
new PackApplicationEvent("Custom Event", this));
});
t.start();
try {
t.join();
} catch (InterruptedException e) {
System.err.printf("Thread interrupted: %s, error: %s%n",
Thread.currentThread().getName(), e.getMessage());
}
}
}With this change the container starts normally and the event is published and listened to without deadlock.
The afterSingletonsInstantiated method is invoked by DefaultListableBeanFactory.preInstantiateSingletons() after all singleton beans have been created.
2.2 Upgrade Spring Version
Another option is to use Spring 6.2 (snapshot) which initializes beans with a multithreaded approach, avoiding the lock contention that caused the deadlock.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
