Skip to main content

Taming Tiger: Default exception handling in threads

What to do with uncaught exceptions

John Zukowski (jaz@zukowski.net), President, JZ Ventures, Inc.
Author photo
John Zukowski conducts strategic Java consulting with JZ Ventures, Inc. and is working with SavaJe Technologies to develop a next-generation mobile phone platform. His latest books are Mastering Java 2, J2SE 1.4 (Sybex, April 2002) and Learn Java with JBuilder 6 (Apress, March 2002). Reach him at jaz@zukowski.net.

Summary:  Tracking down unexpected runtime exceptions can be a real drag, and just getting the default thread name and stack trace isn't always enough. In this installment of Taming Tiger, Java developer John Zukowski shows how you can customize the output by replacing the default behavior. He also contrasts the old way of customizing output by subclassing ThreadGroup with the new way by providing your own custom UncaughtExceptionHandler.

View more content in this series

Date:  10 Aug 2004
Level:  Introductory
Activity:  5252 views
Comments:  

While we don't like to create programs that throw runtime exceptions at unexpected times, it happens -- especially when running complex programs for the first time. Typically, these exceptions are handled by using the default behavior, printing out a stack dump, and ending the life of the thread.

Where do you find out what that default behavior is? Every thread belongs to what is a thread group, represented by the java.lang.ThreadGroup class. As the class name might have you think, thread groups allow you to group threads together. You might group them for purposes of convenience -- for instance, all the threads in a thread pool belong to group X, while those in another pool belong to group Y -- or for access control. Threads in group X don't have permission to access or alter threads in group Y, unless within the same thread group (or a child group).

Prior to Tiger, the ThreadGroup class provided one way to handle uncaught exceptions: the uncaughtException() method of ThreadGroup. If the exception wasn't ThreadDeath, you got the name of the thread and a stack backtrace sent to System.err. But Tiger adds an additional way: the Thread.UncaughtExceptionHandler interface. Either subclassing ThreadGroup or installing an implementation of this new interface allows you to change that default behavior. We'll look at both, before and after Tiger.

Custom behavior with ThreadGroup

When an uncaught exception occurs, the default behavior is to print a stack dump to system error (System.err), as shown in Listing 1. Just start the program with no command line arguments.


Listing 1. Thread dump sample
public class SimpleDump {
  public static void main(String args[]) {
    System.out.println(args[0]);
  }
}

Running this program with no arguments generates the output in Listing 2. It's not that big of a stack trace, but it is, nonetheless, a complete one.


Listing 2. Default thread dump output
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 0
        at SimpleDump.main(SimpleDump.java:3)

As with many things for the Java platform, if you don't like the default behavior, you can change it. Prior to the Tiger release of the Java platform, you couldn't replace the default behavior for all threads, but you could create a new ThreadGroup and change the default behavior for any threads created within that group. Overriding the uncaughtException(Thread t, Throwable e) method, allows you to customize that behavior. Then, any threads created in that thread group will get the new behavior when an unforeseen runtime exception happens. While it is best to fix the underlying problem, I'll provide a simple demonstration that shows the steps necessary to change the default behavior. Listing 3 shows the adjusted test program to place the executing code within a new thread group:


Listing 3. Adjusted thread dump sample
public class WindowDump {
  public static void main(String args[]) throws Exception {
    ThreadGroup group = new LoggingThreadGroup("Logger");
    new Thread(group, "myThread") {
      public void run() {
        System.out.println(1 / 0);
      }
    }.start();
  }
}

The LoggingThreadGroup class is new and its definition is shown in Listing 4. For the demonstration, the special behavior done by the overridden uncaughtException() method is to show the exception in a popup window, which is done by using the Java Logging API with the help of a special Handler.


Listing 4. LoggingThreadGroup definition
import java.util.logging.*;

public class LoggingThreadGroup extends ThreadGroup {
  private static Logger logger;
  public LoggingThreadGroup(String name) {
    super(name);
  }
  public void uncaughtException(Thread t, Throwable e) {
    // Initialize logger once
    if (logger == null) {
      logger = Logger.getLogger("example");
      Handler handler = LoggingWindowHandler.getInstance();
      logger.addHandler(handler);
    }
    logger.log(Level.WARNING, t.getName(), e);
  }
}

The custom Handler created here is of type LoggingWindowHandler, with its definition in Listing 5. The handler uses a support class LoggingWindow, which puts the exception up on the screen. That definition is shown in Listing 6. The public void publish(LogRecord record) method of the Handler provides the important work. The rest is mostly just configuration related.


Listing 5. LoggingWindowHandler definition
import java.util.logging.*;

public class LoggingWindowHandler extends Handler {
  private static LoggingWindow window;
  private static LoggingWindowHandler handler;

  private LoggingWindowHandler() {
    configure();
    window = new LoggingWindow("Logging window...", 400, 200);
  }

  public static synchronized LoggingWindowHandler getInstance() {
    if (handler == null) {
      handler = new LoggingWindowHandler();
    }
    return handler;
  }

  /**
   * Get any configuration properties set
   */
  private void configure() {
    LogManager manager = LogManager.getLogManager();
    String className = getClass().getName();
    String level = manager.getProperty(className + ".level");
    setLevel((level == null) ? Level.INFO : Level.parse(level));
    String filter = manager.getProperty(className + ".filter");
    setFilter(makeFilter(filter));
    String formatter =
      manager.getProperty(className + ".formatter");
    setFormatter(makeFormatter(formatter));
  }

  private Filter makeFilter(String name) {
    Filter f = null;
    try {
      Class c = Class.forName(name);
      f = (Filter)c.newInstance();
    } catch (Exception e) {
      if (name != null) {
        System.err.println("Unable to load filter: " + name);
      }
    }
    return f;
  }

  private Formatter makeFormatter(String name) {
    Formatter f = null;
    try {
      Class c = Class.forName(name);
      f = (Formatter)c.newInstance();
    } catch (Exception e) {
      f = new SimpleFormatter();
    }
    return f;
  }

  // Overridden abstract Handler methods
  
  public void close() {
  }

  public void flush() {
  }

  /**
   * If record is loggable, format it and add it to window
   */
  public void publish(LogRecord record) {
    String message = null;
    if (isLoggable(record)) {
      try {
        message = getFormatter().format(record);
      } catch (Exception e) {
        reportError(null, e, ErrorManager.FORMAT_FAILURE);
        return;
      }
      try {
        window.addLogInfo(message);
      } catch (Exception e) {
        reportError(null, e, ErrorManager.WRITE_FAILURE);
      }
    }
  }
}




Listing 6. LoggingWindow definition
import java.awt.*;
import javax.swing.*;

public class LoggingWindow extends JFrame {
  private JTextArea textArea;

  public LoggingWindow(String title, final int width,
                                           final int height) {
  super(title);
  EventQueue.invokeLater(new Runnable() {
    public void run() {
      setSize(width, height);
      textArea = new JTextArea();
      JScrollPane pane = new JScrollPane(textArea);
        textArea.setEditable(false);
        getContentPane().add(pane);
        setVisible(true);
      }
    });
  }

  public void addLogInfo(final String data) {
    EventQueue.invokeLater(new Runnable() {
      public void run() {
        textArea.append(data);
      }
    });
  }
}

Executing the WindowDump program in Listing 3, produces the screen in Figure 1. The stack dump still appears on the console, because the console handler wasn't removed from the Logger.


Figure 1. Stack trace logged
Stack Trace Logged

It probably seems like quite a bit of work to change what happens when a runtime exception occurs. True, much of this code is the Logging Handler, but to get the changes to execute, you have to subclass ThreadGroup, override uncaughtException(), and have your thread execute in that thread group. Instead, let's look at the Tiger way of doing things, by just installing a Thread.UncaughtExceptionHandler.


Custom behavior with UncaughtExceptionHandler

Added to the Thread class definition for Tiger is a new public inner class, UncaughtExceptionHandler, or more completely known as Thread.UncaughtExceptionHandler (when other classes access the inner class, the full name is necessary). The definition of the interface is the single method shown in Listing 7:


Listing 7. UncaughtExceptionHandler definition
public interface Thread.UncaughtExceptionHandler {
    public void uncaughtException(Thread, Throwable);
}

In case you didn't notice, the method in Listing 7 is the same method of ThreadGroup that we previously overrode. In fact, the ThreadGroup class now implements the interface.

This new inner class helps us to understand and use the following two pairs of new methods in Thread:

  • getUncaughtExceptionHandler() and setUncaughtExceptionHandler()
  • getDefaultUncaughtExceptionHandler() and setDefaultUncaughtExceptionHandler()

The first pair, getUncaughtExceptionHandler() and setUncaughtExceptionHandler(), allows you to customize the behavior for the current thread and its descendants, which in turn allows twenty or more threads to each have their own custom behavior. More likely though, you'll use the second pair, getDefaultUncaughtExceptionHandler() and setDefaultUncaughtExceptionHandler(). If the default handler is set with the latter pair of methods, any thread that doesn't have its own exception handler will use the default.

Sounds simple. To demonstrate, Listing 8 converts the ThreadGroup-friendly program in Listing 3 to use the new UncaughtExceptionHandler interface:


Listing 8. UncaughtExceptionHandler example
public class HandlerDump {
  public static void main(String args[]) throws Exception {
    Thread.UncaughtExceptionHandler handler = new LoggingThreadGroup("Logger");
    Thread.currentThread().setUncaughtExceptionHandler(handler);
    System.out.println(1 / 0);
  }
}

Instead of creating a new handler implementation, the program simply reuses the LoggingThreadGroup as an UncaughtExceptionHandler. Notice how much cleaner the new code is as compared to the old.


Additional thread changes

The Thread class didn't just get support for uncaught exception handlers added with Tiger. In addition, you'll find support for getting the stack trace of all live threads with getAllStackTraces() or just the current thread with getStackTrace(). These both return objects of type java.lang.StackTraceElement, a class added in the Java 1.4 platform that lets you to generate your own stack traces. Also new to the Java 5 platform is a unique thread identifier, available with getId(), and a new Thread.State class and related getThreadState() method. This last one is an enumeration of states for monitoring system states, not synchronization.


Summary

Simple library changes like adding an uncaught exception handler greatly increase the comprehensibility of source code. While functionally equivalent to older library code at the thread group level, the ease of use and flexibility in the new model far exceeds any time requirements to adjust code to the newer way. Sure, the old way will still work, but updating code to the latest library functionality is typically best.



Download

NameSizeDownload method
j-tiger08104source.zip10KB HTTP

Information about download methods


Resources

About the author

Author photo

John Zukowski conducts strategic Java consulting with JZ Ventures, Inc. and is working with SavaJe Technologies to develop a next-generation mobile phone platform. His latest books are Mastering Java 2, J2SE 1.4 (Sybex, April 2002) and Learn Java with JBuilder 6 (Apress, March 2002). Reach him at jaz@zukowski.net.

Comments



Trademarks  |  My developerWorks terms and conditions

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology
ArticleID=15044
ArticleTitle=Taming Tiger: Default exception handling in threads
publish-date=08102004
author1-email=jaz@zukowski.net
author1-email-cc=jaloi@us.ibm.com

My developerWorks community

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere).

My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Special offers