Automatically detecting AWT event dispatch thread hangs

I mentioned Apple's "Spin Control" recently, which detects hung Cocoa applications. It doesn't work for Java applications because they're packed full of multithreaded goodness, and the main AppKit thread doesn't tend to get stuck. The problematic thread in a Java application is the AWT's event dispatch thread. Do too much work on that, and your GUI will become unresponsive.

I've been meaning to write something similar for Java ever since I read the changes in the first 1.5 beta and saw that it was going to be possible to get a stack trace for a different thread. Unfortunately, it's taken Apple until now to give me a Java 1.5 implementation.

The principle is simple. We push our own EventQueue implementation on top of the system's one. That makes a note of when we start to dispatch an event before calling the normal dispatch code. There's a timer (a java.util one, not a javax.swing one, because that would try to call us back on the event thread, which would be useless) going off every 100 ms. Each time the timer fires, we see how long we've been dispatching the current event. If it's taken us more than 500 ms, which is long enough for a human to notice, though not particularly annoying in most circumstances, we dump a stack trace for the event dispatch thread, showing where it's got to. When the normal dispatch code returns, if we dumped a stack trace, we print a message saying how long it took in total.

Experience with similar watchdog systems on other systems suggests that a single stack trace as soon as you notice a problem is usually sufficient to find and fix the problem.

To use this in your own programs, just call the static initMonitoring method on EventDispatchThreadHangMonitor and you're away.

Here's an example of the output when I put Thread.sleep(2500) in one of my applications' Action subclasses:

--- event dispatch thread hung for 502 ms:
java.lang.Thread.sleep(Native Method)
--- event dispatch thread unstuck after 2506 ms.

And here's the code. This is LGPL, and the latest version is available in the (stupidly named) salma-hayek library.

package e.debug;

import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.*;

* Monitors the AWT event dispatch thread for events that take longer than
* a certain time to be dispatched.
* The principle is to record the time at which we start processing an event,
* and have another thread check frequently to see if we're still processing.
* If the other thread notices that we've been processing a single event for
* too long, it prints a stack trace showing what the event dispatch thread
* is doing, and continues to time it until it finally finishes.
* This is useful in determining what code is causing your Java application's
* GUI to be unresponsive.
* @author Elliott Hughes <enh@jessies.org>
public final class EventDispatchThreadHangMonitor extends EventQueue {
private static final EventQueue INSTANCE = new EventDispatchThreadHangMonitor();

// Time to wait between checks that the event dispatch thread isn't hung.
private static final long CHECK_INTERVAL_MS = 100;

// Maximum time we won't warn about.
private static final long UNREASONABLE_DISPATCH_DURATION_MS = 500;

// Used as the value of startedLastEventDispatchAt when we're not in
// the middle of event dispatch.
private static final long NO_CURRENT_EVENT = 0;

// When we started dispatching the current event, in milliseconds.
private long startedLastEventDispatchAt = NO_CURRENT_EVENT;

// Have we already dumped a stack trace for the current event dispatch?
private boolean reportedHang = false;

// The event dispatch thread, for the purpose of getting stack traces.
private Thread eventDispatchThread = null;

private EventDispatchThreadHangMonitor() {

* Sets up a timer to check for hangs frequently.
private void initTimer() {
final long initialDelayMs = 0;
final boolean isDaemon = true;
Timer timer = new Timer("EventDispatchThreadHangMonitor", isDaemon);
timer.schedule(new HangChecker(), initialDelayMs, CHECK_INTERVAL_MS);

private class HangChecker extends TimerTask {
public void run() {
// Synchronize on the outer class, because that's where all
// the state lives.
synchronized (INSTANCE) {

private void checkForHang() {
if (startedLastEventDispatchAt == NO_CURRENT_EVENT) {
// We don't destroy the timer when there's nothing happening
// because it would mean a lot more work on every single AWT
// event that gets dispatched.

private void reportHang() {
if (reportedHang) {
// Don't keep reporting the same hang every 100 ms.

reportedHang = true;
System.out.println("--- event dispatch thread stuck processing event for " + timeSoFar() + " ms:");
StackTraceElement[] stackTrace = eventDispatchThread.getStackTrace();
printStackTrace(System.out, stackTrace);

private void printStackTrace(PrintStream out, StackTraceElement[] stackTrace) {
// We know that it's not interesting to show any code above where
// we get involved in event dispatch, so we stop printing the stack
// trace when we get as far back as our code.
final String ourEventQueueClassName = EventDispatchThreadHangMonitor.class.getName();
for (StackTraceElement stackTraceElement : stackTrace) {
if (stackTraceElement.getClassName().equals(ourEventQueueClassName)) {
out.println(" " + stackTraceElement);

* Returns how long we've been processing the current event (in
* milliseconds).
private long timeSoFar() {
long currentTime = System.currentTimeMillis();
return (currentTime - startedLastEventDispatchAt);

* Sets up hang detection for the event dispatch thread.
public static void initMonitoring() {

* Overrides EventQueue.dispatchEvent to call our pre and post hooks either
* side of the system's event dispatch code.
protected void dispatchEvent(AWTEvent event) {

* Stores the time at which we started processing the current event.
private synchronized void preDispatchEvent() {
if (eventDispatchThread == null) {
// I don't know of any API for getting the event dispatch thread,
// but we can assume that it's the current thread if we're in the
// middle of dispatching an AWT event...
eventDispatchThread = Thread.currentThread();

reportedHang = false;
startedLastEventDispatchAt = System.currentTimeMillis();

* Reports the end of any ongoing hang, and notes that we're no longer
* processing an event.
private synchronized void postDispatchEvent() {
if (reportedHang) {
System.out.println("--- event dispatch thread unstuck after " + timeSoFar() + " ms.");
startedLastEventDispatchAt = NO_CURRENT_EVENT;