1. What is the EDT (Event Dispatch Thread)
In Java graphical applications — whether Swing or JavaFX — all user actions (clicks, keystrokes) as well as window repainting are handled in a special thread called the EDT (Event Dispatch Thread).
Why do we need it? UI components in Java are not thread-safe. To avoid races and artifacts, all UI changes are performed strictly in one place — on the EDT. It’s like a checkout with a single cashier: the same receipt cannot be handled by multiple people at the same time.
In Swing, the EDT runs event handlers and component repainting (for example, actionPerformed). In JavaFX, the counterpart is the JavaFX Application Thread, where UI updates and handlers like setOnAction are executed.
2. The problem of long-running operations in the UI
What happens if you start a long-running operation on the EDT?
When the user clicks a button, the handler (for example, actionPerformed or setOnAction) executes on the EDT. If you start a heavy task inside it (reading a large file, a network request, complex computations), the entire UI “freezes”:
- The window stops responding to clicks and keystrokes.
- Repainting stops—when you move it, the window appears “stuck.”
- The user decides the program has “crashed.”
Incorrect code example (Swing):
button.addActionListener(e -> {
// Long operation right in the EDT!
longOperation(); // For example, reading a large file
label.setText("Done!");
});
Result: while longOperation() is running, the window does not respond to the user.
Why? The EDT processes tasks sequentially and can execute only one at a time. While it is busy with your long operation, it cannot process clicks or repainting.
3. Solution: long-running operations should run only in background threads
Principle:
- All long-running operations belong in background threads.
- All UI changes belong only on the EDT/JavaFX Application Thread.
Run a long operation in a separate thread
Example (Swing):
button.addActionListener(e -> {
new Thread(() -> {
longOperation(); // Runs in a background thread
// Now we need to update the UI — but only from the EDT!
SwingUtilities.invokeLater(() -> label.setText("Done!"));
}).start();
});
Example (JavaFX):
button.setOnAction(e -> {
new Thread(() -> {
longOperation();
// Update the UI via Platform.runLater
Platform.runLater(() -> label.setText("Done!"));
}).start();
});
How to update the UI from a background thread?
- Swing: use SwingUtilities.invokeLater(Runnable) — the task will be placed in the EDT queue.
- JavaFX: use Platform.runLater(Runnable) — the task will run on the JavaFX Application Thread.
Why can’t you just call label.setText(...) from a background thread? Because that violates UI thread-safety: components must be modified only from the UI thread.
Special classes for background tasks
In real applications, you often need to show progress, allow cancellation, and handle errors. For this, there are:
- SwingWorker<T, V> — for Swing;
- Task<V>, Service<V> — for JavaFX.
Example (JavaFX Task):
Task<Void> task = new Task<>() {
@Override
protected Void call() throws Exception {
longOperation();
// You can update progress: updateProgress(...)
return null;
}
};
task.setOnSucceeded(e -> label.setText("Done!"));
task.setOnFailed(e -> label.setText("Error!"));
new Thread(task).start();
Benefits: progress, cancellation, success/failure events. UI changes — via safe methods (updateMessage, updateProgress) or handlers (setOnSucceeded, etc.).
4. Correct and incorrect patterns
Incorrect: long operations in the event handler
button.setOnAction(e -> longOperation()); // The UI will freeze!
Correct: long operations in a separate thread
button.setOnAction(e -> new Thread(() -> longOperation()).start());
Even better: use Task/Worker
JavaFX:
button.setOnAction(e -> {
Task<Void> task = new Task<>() {
@Override
protected Void call() throws Exception {
longOperation();
return null;
}
};
task.setOnSucceeded(ev -> label.setText("Done!"));
new Thread(task).start();
});
Swing:
button.addActionListener(e -> {
SwingWorker<Void, Void> worker = new SwingWorker<>() {
@Override
protected Void doInBackground() throws Exception {
longOperation();
return null;
}
@Override
protected void done() {
label.setText("Done!");
}
};
worker.execute();
});
5. Practice: a file loading example
JavaFX:
button.setOnAction(e -> {
Task<String> task = new Task<>() {
@Override
protected String call() throws Exception {
// Simulating a long load
Thread.sleep(2000);
return "File loaded!";
}
};
task.setOnSucceeded(ev -> label.setText(task.getValue()));
new Thread(task).start();
});
Swing:
button.addActionListener(e -> {
SwingWorker<String, Void> worker = new SwingWorker<>() {
@Override
protected String doInBackground() throws Exception {
Thread.sleep(2000);
return "File loaded!";
}
@Override
protected void done() {
try {
label.setText(get());
} catch (Exception ex) {
label.setText("Error!");
}
}
};
worker.execute();
});
6. Common mistakes when working with the EDT and long operations
Mistake No. 1: A long operation on the EDT. The entire application “freezes,” the window doesn’t respond, and the user thinks the program is broken.
Mistake No. 2: Attempting to update the UI from a background thread. Violating UI thread-safety can lead to bugs, artifacts, and crashes. Use SwingUtilities.invokeLater or Platform.runLater.
Mistake No. 3: No error handling in the background task. Exceptions get “lost,” and the user doesn’t know what went wrong. In Swing — override done() and read get(); in JavaFX — subscribe to setOnFailed.
Mistake No. 4: No way to cancel a long operation. The user cannot stop loading/computation. Use cancellation support (SwingWorker.cancel, Task.cancel) and check cancellation flags inside the task.
Mistake No. 5: No progress indication. The user thinks the program has “frozen.” In Swing — use result publishing and a progress bar together with SwingWorker; in JavaFX — updateProgress and visual indicators.
GO TO FULL VERSION