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
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()andsetUncaughtExceptionHandler()getDefaultUncaughtExceptionHandler()andsetDefaultUncaughtExceptionHandler()
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.
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.
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.
| Name | Size | Download method |
|---|---|---|
| j-tiger08104source.zip | 10KB | HTTP |
Information about download methods
- Read the complete Taming Tiger series by John Zukowski.
- Download the J2SE 5.0 from the Sun Developer Network.
- Read the
Threadclass javadoc. - Read the
ThreadGroupclass Javadoc. - Read the java.util.logging package javadocs for the Java 5 platform.
- Learn how to avoid thread leakage in server applications in Brian Goetz's Java theory and practice column, "Hey, where'd my thread go?" (developerWorks, September 2002).
- Learn the principles of threading in the "Introduction to Java threads" tutorial (developerWorks, September 2002).
- Learn about the Logging API in the earlier Magic with Merlin column "Exceptions and logging" (developerWorks, December 2001).
- Examine the progress of Tiger (J2SE 5) with JSR 176.
- Email bug reports to Sun at j2se-beta-feedback@sun.com.
- Find hundreds more Java technology resources on the
developerWorks Java technology zone.
- Browse for books on these and other technical topics.

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.





