Java Specialists' Java Training Europehome of the java specialists' newsletter

The Java Specialists' Newsletter
Issue 0812003-11-25 Category: Exceptions Java version:

GitHub Subscribe Free RSS Feed

Catching Exceptions in GUI Code

by Dr. Heinz M. Kabutz

Welcome to the 81st edition of The Java(tm) Specialists' Newsletter. I can hardly believe that it is now already over six weeks since I wrote the last newsletter. Since then, the Grupo de Usu¨¢rios Java - GUJ, from Brazil has started translating some of our newsletters into Portuguese. You can see the result on our archive webpage. Please send us an email if you would like to translate the newsletter into your language.

Design Patterns are the key to really understanding object orientation. What is the difference between the Gang-of-Four Design Patterns and J2EE Patterns? The Gang-of-Four patterns represent the basic building blocks of object orientation. Most of the patterns rely on polymorphism. The J2EE patterns, on the other hand, are design solutions for the J2EE architecture. They are at a far higher level than the Gang-of-Four patterns. Knowing the Gang-of-Four patterns will help you understand how the J2EE patterns work.

NEW: Please see our new "Extreme Java" course, combining concurrency, a little bit of performance and Java 8. Extreme Java - Concurrency & Performance for Java 8.

Catching Exceptions in GUI Code

One of the reasons that I continuously promote Design Patterns is that they really work. A pattern that I enjoy teaching is the Command pattern, because the exercise with this pattern is particularly hard unless your mind has already been altered. During our discussion time around that pattern, I usually pose this questions: "What happens when our command causes an exception?"

The answer is not that obvious. The invoker usually does not know what to do with an uncaught exception. Unless you have an alternative exception listening mechanism in place, the exception will simply be lost.

Let us take the following GUI as an example:

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;

public class Gui extends JFrame {
  private static final int[] DAYS_PER_MONTH =
      {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
  public Gui() {
    super("GUI Example");
    final JTextArea text = new JTextArea(14, 30);
    getContentPane().add(new JScrollPane(text));
    getContentPane().add(new JButton(new AbstractAction("Calculate") {
      public void actionPerformed(ActionEvent e) {
        text.setText("");
        for (int i=0; i<=DAYS_PER_MONTH.length; i++) {
          text.append("Month " + (i+1) + ": " +
              DAYS_PER_MONTH[i] + "\n");
        }
      }
    }), BorderLayout.NORTH);
  }
  public static void main(String[] args) {
    Gui gui = new Gui();
    gui.pack();
    gui.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    gui.show();
  }
}
  

Please compile and run this program using javaw.exe instead of java.exe. i.e. type the following:

  javaw Gui

This will start the Gui frame and wait for you to click the button. Now it gets really interesting! When you press the button, you see the months and days per month appearing on the textpane. It all seems good, except that the button looks like it stays depressed.

Please run the program again using java.exe, and when you press the button you will see the following exception on the console:

java.lang.ArrayIndexOutOfBoundsException: 12
  at Gui$1.actionPerformed(Gui.java:16)
  at javax.swing.AbstractButton.fireActionPerformed
  at javax.swing.AbstractButton$ForwardActionEvents.actionPerformed
  at javax.swing.DefaultButtonModel.fireActionPerformed
  at javax.swing.DefaultButtonModel.setPressed
  at javax.swing.plaf.basic.BasicButtonListener$ReleasedAction.actionPerformed
  at javax.swing.SwingUtilities.notifyAction
  at javax.swing.JComponent.processKeyBinding
  at javax.swing.JComponent.processKeyBindings
  at javax.swing.JComponent.processKeyEvent
  at java.awt.Component.processEvent
  at java.awt.Container.processEvent
  at java.awt.Component.dispatchEventImpl
  at java.awt.Container.dispatchEventImpl
  at java.awt.Component.dispatchEvent
  at java.awt.KeyboardFocusManager.redispatchEvent
  at java.awt.DefaultKeyboardFocusManager.dispatchKeyEvent
  at java.awt.DefaultKeyboardFocusManager.preDispatchKeyEvent
  at java.awt.DefaultKeyboardFocusManager.typeAheadAssertions
  at java.awt.DefaultKeyboardFocusManager.dispatchEvent
  at java.awt.Component.dispatchEventImpl
  at java.awt.Container.dispatchEventImpl
  at java.awt.Window.dispatchEventImpl
  at java.awt.Component.dispatchEvent
  at java.awt.EventQueue.dispatchEvent
  at java.awt.EventDispatchThread.pumpOneEventForHierarchy
  at java.awt.EventDispatchThread.pumpEventsForHierarchy
  at java.awt.EventDispatchThread.pumpEvents
  at java.awt.EventDispatchThread.pumpEvents
  at java.awt.EventDispatchThread.run

Besides printing the thread stack trace to the console, the Java event dispatch thread also does another thing: it dies! I only discovered that recently. The dispatch thread dying is probably the reason that the button stays depressed. If you look in the EventDispatchThread code, you will see that a new thread is only created once more Gui events occur. Unless you move the mouse, or press a key, no events will happen and so the button stays depressed.

We can verify that the event dispatch thread dies by printing out the System.identityHashCode() of the thread that executes the actionPerformed method (this should be the EventDispatchThread). In this example, the exception occurs after the button is pressed for the fourth time.

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;

public class ThreadsDie extends JFrame {
  public ThreadsDie() {
    super("GUI Example");
    final JTextArea text = new JTextArea(14, 30);
    getContentPane().add(new JScrollPane(text));
    getContentPane().add(new JButton(new AbstractAction("Calculate") {
      private int countdown = 3;
      public void actionPerformed(ActionEvent e) {
        text.append("Event Queue Thread: " +
            System.identityHashCode(Thread.currentThread()));
        text.append("\n");
        if (--countdown <= 0) {
          throw new IllegalArgumentException();
        }
      }
    }), BorderLayout.NORTH);
  }
  public static void main(String[] args) {
    ThreadsDie gui = new ThreadsDie();
    gui.pack();
    gui.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    gui.show();
  }
}

The output on the textpane is something like:

  Event Queue Thread: 29959477
  Event Queue Thread: 29959477
  Event Queue Thread: 29959477
  Event Queue Thread: 23994289
  Event Queue Thread: 19764978
  Event Queue Thread: 1114115
  Event Queue Thread: 1565898
  Event Queue Thread: 11383252
  Event Queue Thread: 23110255
  Event Queue Thread: 21514757

Creating a new thread each time you get an exception in your Gui is not that good, since it is fairly expensive to create threads. We would like to get to the position where no exceptions are generated by our Gui at all.

Last Friday I went to a customer who had an old version of the software I am working on at the moment. When it started up, I saw several exceptions appear on the console. On questioning him about that he said: "Oh those, they always appear. The program still works though."

This makes me scared. I would rather get 259 emails in my inbox telling me that something is seriously amiss, than have my customers think that seeing exceptions on the console is acceptable.

However, how can we catch all uncaught exceptions that are generated in the Gui?

The trick lies in a class called the ThreadGroup and in the way that JDK 1.4 now constructs the Gui threads. A Thread may belong to a ThreadGroup. ThreadGroup has a method called uncaughtException(Thread t, Throwable e) that we can override and which is called whenever an uncaught exception occurs. The event dispatch thread still dies, but at least we know about it.

import javax.swing.*;
import java.awt.*;

public class ExceptionGroup extends ThreadGroup {
  public ExceptionGroup() {
    super("ExceptionGroup");
  }
  public void uncaughtException(Thread t, Throwable e) {
    JOptionPane.showMessageDialog(findActiveFrame(),
        e.toString(), "Exception Occurred", JOptionPane.ERROR_MESSAGE);
    e.printStackTrace();
  }
  /**
   * I hate ownerless dialogs.  With this method, we can find the
   * currently visible frame and attach the dialog to that, instead
   * of always attaching it to null.
   */
  private Frame findActiveFrame() {
    Frame[] frames = JFrame.getFrames();
    for (int i = 0; i < frames.length; i++) {
      Frame frame = frames[i];
      if (frame.isVisible()) {
        return frame;
      }
    }
    return null;
  }
}

All we now need to do is start the Gui from within a Thread that belongs to this ExceptionGroup, and we will catch all uncaught exceptions that are caused by the event dispatch thread:

import javax.swing.*;

public class BetterGui {
  public static void main(String[] args) {
    ThreadGroup exceptionThreadGroup = new ExceptionGroup();
    new Thread(exceptionThreadGroup, "Init thread") {
      public void run() {
        Gui gui = new Gui();
        gui.pack();
        gui.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        gui.show();
      }
    }.start();
  }
}

This solution is far from perfect. In my industrial-strength exception framework (not available for general use, don't ask), the uncaught exceptions go to a central place from where they are dispatched to the correct listeners. Those might be more fancy dialogs, or emails sent to the support team, or a stacktrace in a log file.

That is it for today. I need some beauty sleep so that tomorrow I can tackle all the exceptions that we found with this approach in some existing code.

Kind regards

Heinz

P.S. Please don't forget to contact me if you would like to translate The Java(tm) Specialists' Newsletter into your native language

Exceptions Articles Related Java Course

Java Master
Java Concurrency
Design Patterns
In-House Courses



© 2010-2014 Heinz Kabutz - All Rights Reserved Sitemap
Oracle and Java are registered trademarks of Oracle and/or its affiliates. Other names may be trademarks of their respective owners. JavaSpecialists.eu is not connected to Oracle, Inc. and is not sponsored by Oracle, Inc.
@CORE_THE_BAND #RBBJGR