January 23, 2021
Hot Topics:

Managing Long-Running Tasks in a Swing App

  • By Rob Lybarger
  • Send Email »
  • More Articles »

The "taskButton" and "textComponent" are just references to widgets on the main UI display. (The design philosophy and wiring logic was presented in the original article.)

The first method is called in the event dispatch thread by the command button's anonymous ActionListener implementation. (Or, perhaps it is triggered programmatically via a direct call.) Because the task being run will utilize a StringBuffer to reflect its data state, a new one is created, and then wrapped inside a new TaskInfo object. A new task instance is created to actually perform the task, and then a simple anonymous Runnable is implemented to call the second method shown. (Were this a C-style language, a function-pointer might make sense.)

This second method receives the same TaskInfo object given to the task, and updates the UI appropriately depending on the the final state of the task. Here, everything just goes to the textComponent object to either show the message, confirm to the user the task was aborted, or display an exception message.

Both of these Runnable objects are then handed off to another helper class I will describe next.

The TaskController

This helper class completes the operational cycle: It starts the actual task in a sub-thread and then waits for it to finish. Once the sub-thread dies (either via completion, being interrupted, or due to results of an exception), the "doneHandler" is registered to be executed back in the event dispatch thread.

public class TaskController extends Thread {

   private Runnable task;
   private Runnable doneHandler;

   public TaskController(Runnable task,
                         Runnable doneHandler) {
      this.task = task;
      this.doneHandler = doneHandler;

   public void run() {
      Thread taskThread =
         (task instanceof Thread ?
            (Thread)task :
            new Thread(task));

      try {
      catch (InterruptedException e) {

About that conditional assignment operator: The sub-thread that is started is either a type cast of the Runnable (should the "task" in question have actually extended from the Thread class directly) or is a new Thread. This gives a little flexibility in how you actually implement the "task" code. Also, notice that TaskController is completely agnostic of what it is managing: It only cares to start a Thread to "do something," and then have the event dispatch thread invoke a Runnable to "do something else."

The above classes (TaskInfo, TaskController) and method pair in the AppController class form the backbone of this "long running task" management design. Going back to the original list, you've already accounted for several design points:

  • Uses the AppController for coordination.
  • Flexible invocation of task (button press or programmatic call).
  • Task runs in thread separate from event dispatch thread.
  • Task's many possible exit states are collected and displayed.

The remaining pieces are largely window dressing that you could probably add yourself, but I'll cover them for sake of completeness.

The Remaining Bits

Cancel Operation

Adding the ability to cancel the operation involves storing a reference to the TaskController as an instance variable in the AppController class, adding a public method called, say, handleCancelTaskAction, and having an extra button on the interface somewhere that calls this cancel method.

Note the source code for this demo takes a slightly different route to get to the same end point. It uses a standard JOptionPane (with the "OK_CANCEL_OPTION" button set) and registers a window listener to detect the JOptionPane's dialog has been closed. Then, depending on the result value of the JOptionPane, the current task is either allowed to continue running or is interrupted. (Admittedly, this was partly a result of laziness to quickly get something to illustrate the cancel operation for this article.)

Task Working Hint

Although you have a fairly static hint already in place that the task was started—the task button is disabled and the cancel button is enabled—a more dynamic display always adds a sense of confidence to the user. Even if you, the programmer, know this is a false sense of confidence, it is amazing how much more "patience" a user has when the UI shows some apparent sense of progress, or even just action in general. Perhaps the easiest way to accomplish this is to provide a JProgressBar somewhere on the interface. You do not need to know how long the task will take and compute a "percent complete" metric to make use of it, either: Simply call the setIndeterminate(boolean) method, in the same places you are toggling the state of the command buttons, to cause it to enter or leave an animated "bouncing box" display. (I remember writing a custom component to do this just slightly before JProgressBar exhibited this particular trick.) Of course, if you do have a way of communicating more information than this, you might want to include that as well.

Taking Longer than Expected

This, too, is fairly easy to accomplish. With a minor modification of the TaskController, you can use a variation of the join method that only blocks for a limited amount of time. When your code resumes, see whether the task sub-thread is still running. If it is, inform the user somehow (you might want a method in the AppController which results in a dialog box being shown, for example) and then re-join the sub-thread:

public void run() {
  try {
    if (taskThread.isAlive()) {
      SwingUtilities.invokeLater(new Runnable() {
        public void run() {

Page 3 of 4

This article was originally published on January 24, 2008

Enterprise Development Update

Don't miss an article. Subscribe to our newsletter below.

Thanks for your registration, follow us on our social networks to keep up-to-date