Since its inception in JDK 1.2, the maturing Swing toolkit has become an attractive alternative to thin-client presentation technologies such as JavaServer Pages (JSP), JavaScript, and HTML. Swing offers the benefits of portability, code reusability, and ease of maintenance, especially when the solution requires a rich user interface (UI) that is deployable in a controlled environment. For instance, Swing developers need not worry about browser compatibility issues; they can instead devote more time to improving component functionality and application usability.

Despite these favorable attributes, the Swing toolkit has weaknesses. Swing suffers from the inherent limitation of a larger resource footprint and dependencies on the Java runtime environment, thereby complicating its deployment process. The more important issue is that developers should be aware of the UI design goals and Swing's inner mechanics during a project's early phases. Doing so avoids costly redesigns in later development stages. It is good practice to keep in mind the following design criteria when putting together the application user interface:

  • Screen liveliness: The user interface should maximize its responsiveness to user interaction, especially when the application executes other processing-intensive tasks.
  • Presentation integrity: The user interaction with the application should always obtain consistent and predictable presentation results, unless nondeterminism is part of the application logic.
  • Operation feedback: The user interface should accurately inform users of the current application status through visual feedback. Some examples of basic feedback mechanisms are the progress meter and the mouse cursor change.

A great deal of effort may be necessary to factor out the complexities required to achieve these design requirements. This article eases that effort by illustrating best practices through a demo application.

Note: You can download this article's demo from Resources.

Basic Swing concepts review

Let's review the Swing event-dispatch concept, which is the foundation for other topics discussed later. First, when the user interacts with Swing components, whether it is clicking on a button or resizing a window, the Swing toolkit generates event objects that contain relevant event information, such as event source and event ID. The event objects are then placed onto a single event queue ordered by their entry time. While that happens, a separate thread, called the event-dispatch thread, regularly checks the event queue's state. As long as the event queue is not empty, the event-dispatch thread takes event objects from the queue one by one and sends them to the interested parties. Finally, the interested parties react to the event notification by processing logic such as event handling or component painting. Figure 1 illustrates how this works.

Figure 1. Swing event-dispatch model

Since the event-dispatch thread executes all event-processing logic sequentially, it avoids undesirable situations such as painting a component whose model state is partially updated. This is great news for Swing developers because they can assume that only one thread, the event-dispatch thread, will process the event-handling code. If two event-dispatch threads worked on the event queue, Swing developers would need to write additional code for thread safety. Similarly, executing user interface-related code in any thread other than the event-dispatch thread can lead to unexpected behaviors. If it is absolutely necessary to execute UI-related code from a separate thread, the developer should use the following code as a workaround:

Listing 1. An example of executing code on the event-dispatch thread

Runnable aRunnable = new Runnable()
{
   public void run()
   {
      // UI related code
   }
};
SwingUtilities.invokeLater(aRunnable);

The invokeLater() method creates a special event that wraps around the runnable object and places it on the event queue. When the event-dispatch thread processes that special event, it invokes the runnable object's run method in the same thread context, thus preserving thread safety.

Shortcomings of a single-threaded user interface

While it is possible to write a Swing application without any multithreading code, the resulting user interface often fails to meet the design criteria of screen liveliness. Here is an example to illustrate this point: Consider a simple user interface that includes a table and a text area. As the table's row selection changes through mouse clicks or arrow-key presses, the text area appends a new message to the existing text. Figure 2 shows the demo application screenshot.

Figure 2. Demo application screenshot

Under this normal mode of operation, the user interface is very responsive. The text area updates without any delay as the user makes new table selections. However, the screen liveliness quickly deteriorates if each row selection invokes a time-consuming task before updating the text area. The demo application implements the time-consuming task by simply making the current thread sleep for a random duration:

Listing 2. The code to simulate a time-consuming task

//Generate a random number of seconds between one to four seconds
int sleepTime = (new Random().nextInt(4) + 1) * 1000;
Thread.currentThread().sleep(sleepTime);

This artificial delay demonstrates the negative effect of executing a time-consuming task, which can be a complex SQL statement execution in a more realistic situation. Remember the thread that becomes dormant is the event-dispatch thread, which is also responsible for dispatching painting events. Therefore, when the user makes a new table selection, the user interface ceases to properly repaint itself during the delay. The application will display bizarre effects when the user resizes the window during the delay, as seen in Figure 3 below. During this problematic delay, the user can also queue up additional events by clicking on the unresponsive screen, potentially causing further unintended consequences. Feel free to verify these problems by running the demo application in delay mode.

Figure 3. Effect of time-consuming task clogging the event-dispatch thread

SwingWorker to the rescue

Multithreading code easily resolves the problem described above. If the time-consuming task executes on a separate thread, then the event-dispatch thread can dispatch events freely. In the context of our demo application, the event-handling logic should execute the delay in a new thread for each new table selection event. Any UI-related logic in that thread, such as updating the text area, should still call the SwingUtilities.invokeLater() method, as described in the earlier section:

Listing 3. Execute time-consuming task on a separate thread

Runnable timeConsumingRunnable = new Runnable()
{
   public void run() 
   {
      // Execute time-consuming task
      ...
      // Execute UI updates on event-dispatch thread
      SwingUtilities.invokeLater(
         new Runnable() 
         {
            public void run()
            {
               // Update UI
            }
         };);
    }
};
new Thread(timeConsumingRunnable).start();

Although this is a viable approach, we need to take a step further by introducing a class named SwingWorker, which is downloadable from the Swing Connection. SwingWorker attempts to take the multithreading complexities out of UI-related classes. It provides two methods that developers can override, the construct() and the finished() methods. The SwingWorker class creates a new thread to run the code within the construct() method. The finished() method's code runs on the event-dispatch thread after the construct() method completes. In the event-handling logic, the developer can provide the implementation for these methods in an anonymous class that inherits from the SwingWorker class. Comparing Listing 3 and Listing 4 tells us that the SwingWorker solution is better because it keeps the UI class pure by not cluttering it with lower-level multithreading code such as Runnable and Thread. The demo application's SwingWorker mode shows that the user interface meets the screen liveliness design criteria even while the application executes time-consuming tasks:

Listing 4. Execute time-consuming task using SwingWorker

SwingWorker aWorker = new SwingWorker() 
{
   public Object construct() 
   {
      // Execute time-consuming task
      ...
      return null;
   }
   
   public void finished()
   {
      // Update UI
   }
};
aWorker.start();

SwingWorker weaknesses

Designing applications for screen liveliness using SwingWorker unfortunately leads to undesirable behaviors that break other UI design goals. A meticulous user would have noticed a flaw in the demo application running under the SwingWorker mode. For example, when a user quickly taps the keyboard arrow buttons to select different table rows, there is a chance that the last message displayed in the console won't match the last row selected in the table. The reason is that each delay on the row selection is set to have a random duration. The task in the first row selected can have a longer delay than those of subsequent rows selected, causing the text area to display messages in a randomized sequence out of sync with the selection order.

Such nondeterministic presentation behavior violates the design goal of presentation integrity. Imagine the user's reaction if the application often displays results that don't match the latest request! This is likely to happen in most situations because it's hard to guarantee that all time-consuming tasks have the same execution time. In a database-driven application, SQL statements have different execution times. In a distributed application that sends data across different computers, the network latency can also add significant variance to the execution time. The random duration of task execution isn't a problem if the application doesn't use SwingWorker or multiple threads. But that forces the user to choose from an unpleasant trade-off between an unresponsive user interface and nondeterministic presentation behavior; neither choice is acceptable for a robust application.

Aside from the problem described above, a scalability issue is equally troubling. The problem's root originates from the fact that event generation has no limit. The demo application user can tap the arrow keys in quick successions to generate a flurry of table selection events. Since each table selection event in turn spawns a new thread to execute the time-consuming task, the explosion in the number of threads in the application can severely affect overall performance. In the case of a client/server scenario, the SwingWorker-enabled client can send an enormous number of task requests to a server in a short period of time, thereby crippling the server in a way similar to a denial-of-service attack.

Possible approaches to problem resolution

While the consequences of the SwingWorker-induced problems are quite severe, numerous approaches are at our disposal. Let's analyze the benefits and drawbacks of each.

Conditional-logic approach: It is possible to use conditional logic or listener removal to disable event handling while a time-consuming task executes. For instance, a Boolean flag can be set to false as soon as a SwingWorker task starts to prevent any additional event-handling logic executions. The Boolean flag is reset to true after a SwingWorker task finishes execution. This technique tries to mitigate the presentation integrity problem because the application always waits until a SwingWorker task finishes before executing another task. It resolves the scalability issue by making it impossible to generate more than one event at a time.

However, the conditional-logic approach is undesirable for the following three reasons: The first reason is the lack of operation feedback to inform the user that event handling is disabled. For example, the user can still interact with the UI components and get a false impression that the components are still active. Second, interaction with the UI components can also lead to additional presentation integrity problems. The UI update in SwingWorker can be inconsistent with a selection made during the SwingWorker operation. Lastly, the sprinkling of conditional statements in Java classes creates a patchwork of hard-to-maintain code.

Intelligent-queue approach: The application can create an intelligent queue to track SwingWorker tasks triggered by the user. Several policies can dictate how this queue should behave. For example, a policy can limit the number of active SwingWorkers to ease the processing load. Another policy can discard new SwingWorker tasks or old redundant tasks if a certain number of tasks are already waiting to run in the queue. Lastly, the queue can use a timestamp-based policy to ensure that only the latest SwingWorker task updates the user interface. This timestamp mechanism tags each SwingWorker task with a unique ID so that the UI update logic can filter out the outdated or duplicate task results.

Page 2 of 2

The intelligent-queue approach's main weakness is its implementation complexity. The thread limitation policy may require thread-pool usage, which is a complex concurrent construct to novice developers. The timestamp-based policy implementation needs to carefully consider the exception-handling strategies for cases when the SwingWorker task with the latest timestamp fails to complete, potentially locking up the UI indefinitely. Some kind of operation feedback to indicate the discarding of a SwingWorker event is also missing in this approach.

Is it possible to find an approach that meets the design criteria while keeping the UI code easy to maintain and develop? Let's find out in the next section.

Customized SwingWorker

The SwingWorker class in its original form suffers from lack of control on the number of threads. Instead of using conditional logic, a more elegant way to allow exactly one performing SwingWorker thread is to utilize the concept of GlassPane. Think of the GlassPane as an inactive and transparent layer between the mouse cursor and the user interface. Under special circumstances, the application can activate the GlassPane layer to intercept and consume mouse events. The application should also disable keyboard interactions when the GlassPane is active. One way to achieve that is to have the GlassPane implement the AWTEventListener interface and let it consume keyboard events. This technique alleviates the problems in the original SwingWorker class in two ways. First, the GlassPane allows at most one active SwingWorker thread by absorbing any subsequent user-initiated actions. Second, UI components do not visually respond to user actions, unlike the conditional-logic approach. As long as the GlassPane is active, any user action will not affect the UI components' appearance or state.

Implementing the GlassPane technique requires some additional background information. Each Swing component belongs to a component hierarchy that represents parent-child relationships. For example, a JButton's parent is the JPanel that contains that button. Likewise, the JPanel's parent may be a JFrame that contains that JPanel. The RootPaneContainer type resides at the top of this hierarchy either as a JFrame, JDialog, JApplet, JWindow, or JInternalFrame. To activate the GlassPane on a frame or a dialog, the developer needs to traverse through the component hierarchy and call the setGlassPane(Component) method on the instance that implements the RootPaneContainer interface. This method automatically sets the parameter component to cover the user interface and intercept events when the component is set to be visible.

Recall also that the original SwingWorker class is essentially an intermediary helper class that hides the multithreading complexities from UI developers. Nothing stops the SwingWorker from including the GlassPane technique as part of its responsibilities. The SwingWorker class can use the Gang of Four Template Method design pattern to predefine a sequence of execution that includes the GlassPane activation and the invocation of the methods implemented by anonymous subclasses. As seen in Figure 4's sequence diagram, the customized SwingWorker also renames the construct() and finish() methods to doNonUILogic() and doUIUpdateLogic() methods, respectively. This modification dissuades developers from mistakenly letting UI-related code execute on a separate thread.

Figure 4. A customized SwingWorker class's sequence diagram

Finally, the GlassPane-SwingWorker combination adds the finishing touch by changing the mouse cursor to the hourglass shape during the time-consuming task's execution. This form of operation feedback effectively indicates the application is in processing mode.

When running the demo application under the custom SwingWorker mode, it is apparent the user interface properly satisfies all three requirements defined earlier: the SwingWorker's multithreading capability ensures screen liveliness at all times; the GlassPane technique and the mouse cursor changes preserve presentation integrity and give users proper visual feedback; and furthermore, the customized SwingWorker class shields the complexities from UI developers by encapsulating its implementation details.

Room for improvement

Now that the three fundamental design criteria have been met, the SwingWorker customization's basic foundation is set in place. However, a different project's user interface design requirements may necessitate further modification of the SwingWorker component. Here are some possible considerations:

  • In applications that use modal dialogs, the user should not be able to close the dialog when the SwingWorker is operating in the background. Even when the GlassPane is active, it does not intercept mouse events on the dialog title bar. Therefore, the dialog default close operation should be set to do nothing when the SwingWorker is still running.
  • There can be some instances where users are allowed to interact with the UI components while the time-consuming task executes. For example, an intermediate message box may pop up to prompt for a user selection. In such cases, the GlassPane does not interfere with the user's mouse interaction because it masks the parent dialog, not the intermediate dialog. However, the GlassPane will absorb keyboard presses, disabling actions such as the mnemonic key or the tab key. To get around that, it may be desirable to improve the logic in the AWTEventListener so that certain key presses are allowed to pass through the GlassPane. For the sake of keeping GlassPane generic, each intermediate dialog should implement an interface that informs the GlassPane of the allowable keys.
  • In cases where a section of event-handling logic requires calling more than one SwingWorker thread, the application may need to serialize the thread initiation sequence. If developers are comfortable with multithreading programming, concurrent constructs such as latches and futures can be useful. Otherwise, it might be easier to avoid designing such requirements into the application.

Other scenarios than the three mentioned above may exist, but in general, developers should find it easy to add their own custom behavior into the SwingWorker class.

Swing away

Swing developers face increasingly demanding UI requirements, and this article presents some reusable components to ease their development efforts. This article lists three important UI design goals as the motivation for customizing the SwingWorker component, and also analyzes two alternative solutions that are less suitable. UI developers can certainly apply the proven techniques described here and take their next Swing-based project to a higher level.

While earning a master's of engineering at Cornell University, Yexin Chen cofounded a startup that offered portal services for college students. He is currently an IBM Global Services IT consultant, designing and developing enterprise applications. Some of his certifications include UML, XML, Sun Certified Java Developer, and jCert Enterprise Developer. Yexin thanks Brent Cooley, Tom Polk, Donna Cognon, and Thomas Callahan for numerous Swing design discussions while working together on a project in Albany, N.Y.

Learn more about this topic