Java Tip 91: Use nested exceptions in a multitiered environment

Automatically capture the context of remotely thrown exceptions through nesting

In this brave new world of business computing, multitiered applications based on RMI, or Remote Method Invocation, running across many computers have overrun the realm of the business application, also commonly known as the enterprise application. Logically separate tiers, such as a GUI tier and a database tier, divide responsibility and isolate the tasks of an enterprise application into well-defined areas that you can develop independently. A popular breakdown today is a three-tiered model: a client tier, which is solely responsible for presentation and user handling of business data; the middle tier, which implements the business logic; and the database tier, which stores and retrieves data. Unfortunately, multitiered applications introduce new debugging challenges over simpler, single-tiered designs.

Handling exceptions generated on remote tiers is one of the challenges enterprise programmers face. An exception bubbling up through the murky depths of multitier code can’t convey the context in which it was thrown, except through its own exception type and error message. Those who haven’t experienced the thrills of remote developing might ask, “What about the stack trace?” (The stack trace refers to the list of methods the executing thread has traversed to the current moment — extremely valuable debugging information that every Java exception stores.) Unfortunately, the RMI framework throws it away when the exception crosses the RMI boundary.

Why is it thrown away? The stack trace is considered a transient property of an exception. When objects are sent across RMI, they are serialized — converted into a stream of bytes that you can easily transmit over a network wire. This means that any object to be transmitted over RMI, including exceptions, must be serializable (by implementing the java.io.Serializable interface). Java programmers use the keyword transient to describe any field of a class that is considered temporary, such as a temporary file handle. Unfortunately for you, transient fields like the exception’s stack trace are not serialized, and thus not reproduced on the new tier.

This makes it difficult to tell when and where a remote exception was thrown, especially when it’s a runtime exception like NullPointerException. Without a stack trace, it’s guesswork. You must either use a remote debugger (usually not an option at the customer’s site) or rely on debugging messages. In terms of giving Joe End-User an error message, forget it. You don’t even know what the problem is.

An example

Imagine you’re in charge of developing an application to manage and configure a security system, which includes several kinds of remote sensors, cameras, and other devices. Each device type has unique configuration commands. Each type reports security information. Information about these devices must be persistent (stored in a database), such as the physical location of the devices, their configuration information, the IP address of the devices, and so forth. Finally, all of the devices must be managed from a remote user interface. This entails configuring the devices, presenting information generated by the devices, and storing information about them.

This design problem begs for a multitier approach. You decide that you’ll use a Java applet for a client. You’ll write a custom application server, in Java, that accepts RMI requests from the applet. So, you need a database and a way to talk to the security devices, which use their own Wacky Security-Device Protocol (WSDP). To that end, you’ll write a class that knows how to talk to them via WSDP, which I call a WSDPBridge. For a technical reason, which I’ll leave unspecified (OK, it makes the example better), the WSDPBridge must exist on another machine from the server, so the server and bridge also communicate through RMI.

To sum up, the design has four components: a Java applet for the client, a database for persisting configuration information, a bridge to talk to the security devices, and a custom server that coordinates all of the above. See Figure 1.

Figure 1. Security device management system as a multitier application

The problem

OK, enough setup. Here’s the problem. Let’s say the user wants to configure a device using the client interface. The client code invokes a remote method on the server that triggers a mess of activity, which can cause a number of errors to occur along the way: the RMI connection from the client to the server can fail; database calls can fail; the RMI connection from the server to the WSDPBridge can fail; communication from the WSDPBridge to the security device can fail; an invalid configuration state can occur. All of these conditions are represented by exceptions: RemoteException, SQLException, WSDPBridgeException, IOException, and InvalidConfigurationException.

The problem lies with the fact that these exceptions are all generated on different remote tiers. The SQLException occurs on the server; the WSDPBridgeExceptions occur on the WSDPBridge (oddly enough), as well as the IOException and InvalidConfigurationException. The RemoteException can happen in two different places: either on the client while trying to connect to the server or on the server while trying to connect to the WSDPBridge. Further, the stack trace is lost as soon as any of these exceptions are serialized.

How can the client handle all these exceptions? A common strategy is for the client applet to catch all exceptions and display the error message in a dialog box. For example, a client might invoke a configure attempt like this:

try {

// we’ve already looked up the server

rmiServer.configureDevice(device, cfg_param, cfg_value);

} catch (Exception e) {

// handleError might pop up a dialog box

handleError(e.getMessage());

}

 

This approach is extremely simple and too limited to be of much use. If you’re lucky, the caught exception was thrown by your code, and you’ve defined unique strings in your exception to help you determine the context. Unfortunately, you must also deal with runtime and third-party exceptions (perhaps you’re using a third-party WSDPBridge). In these cases, you might have no idea what the context of the exception is. It must be inferred, which makes debugging a big problem. For example, you can’t tell whether that RemoteException was signaling a faulty connection between the client and server or between the server and bridge. Don’t you wish you had that stack trace?

Finally, the lack of context makes generating meaningful error messages nearly impossible. Under this approach, client interfaces are usually forced to limp along with a woefully bland error message such as “Configuration failed.” It may also mumble something about contacting the system administrator.

A solution

An elegant solution to the above problem is to use so-called nested exceptions along with a cleanly defined exception structure (which I’ll cover below). Enter the NestingException class. It allows an exception to be stored as a read-only property, overrides the getMessage() method to append the nested exception’s message, and provides a way to sneak that stack trace past the RMI border. If only immigration were this easy.

import java.io.StringWriter; import java.io.PrintWriter;

public class NestingException extends Exception { // the nested exception

private Throwable nestedException; // String representation of stack trace – not transient!

private String stackTraceString; // convert a stack trace to a String so it can be serialized

static public String generateStackTraceString(Throwable t) {

StringWriter s = new StringWriter();

t.printStackTrace(new PrintWriter(s));

return s.toString();

} // java.lang.Exception constructors

public NestingException() {} public NestingException(String msg) {

super(msg);

} // additional c’tors – nest the exceptions, storing the stack trace

public NestingException(Throwable nestedException) {

this.nestedException = nestedException;

stackTraceString = generateStackTraceString(nestedException);

} public NestingException(String msg, Throwable nestedException) {

this(msg);

this.nestedException = nestedException;

stackTraceString = generateStackTraceString(nestedException);

} // methods

public Throwable getNestedException() {return nestedException;} // descend through linked-list of nesting exceptions, & output trace

// note that this displays the ‘deepest’ trace first

public String getStackTraceString() {

// if there’s no nested exception, there’s no stackTrace

if (nestedException == null)

return null; StringBuffer traceBuffer = new StringBuffer(); if (nestedException instanceof NestingException) {

traceBuffer.append(((NestingException)nestedException).getStackTraceString());

traceBuffer.append(“——– nested by:n”);

} traceBuffer.append(stackTraceString);

return traceBuffer.toString();

} // overrides Exception.getMessage()

public String getMessage() {

// superMsg will contain whatever String was passed into the

// constructor, and null otherwise.

String superMsg = super.getMessage(); // if there’s no nested exception, do like we would always do

if (getNestedException() == null)

return superMsg; StringBuffer theMsg = new StringBuffer(); // get the nested exception’s message

String nestedMsg = getNestedException().getMessage(); if (superMsg != null)

theMsg.append(superMsg).append(“: “).append(nestedMsg);

else

theMsg.append(nestedMsg); return theMsg.toString();

} // overrides Exception.toString()

public String toString() {

StringBuffer theMsg = new StringBuffer(super.toString()); if (getNestedException() != null)

theMsg.append(“; nt—> nested “).append(getNestedException()); return theMsg.toString();

} }

 

You can see that it overrides the two constructors of the java.lang.Exception, and adds two of its own, which take the nested exception as an argument and store it. The overridden getMessage() method builds a message by concatenating the message from each nested exception in the chain. Finally, a static method helps fool RMI into passing you the stack trace by converting it into a nontransient String. This method is called when the NestedException is constructed. The instance method getStackTraceString() provides a way to get the complete stack trace, all the way down the nested chain.

Applying nesting exceptions

Let’s look at how the client looks, using nesting exceptions. Instead of catching just the base Exception, you first catch ConfigureException, which extends the NestingException. I’ll look closer at the ConfigureException below, but the client code looks like this:

try {

rmiServer.configureDevice(device, cfg_param, cfg_value);

} catch (ConfigureException e) {

// handleError might pop up a dialog box

handleError(e.getMessage());

} catch (Exception e) {

// handle RemoteException & runtime exceptions

}

 

Here is the server implementation of configureDevice(). This is where the magic happens. Note that the server throws only two exceptions: the RemoteException (which all remote methods must throw) and the newly defined ConfigureException. Pay attention to what happens when you catch the various exceptions that can be thrown.

public void configureDevice(Device dev, String param, String value)

throws RemoteException, ConfigureException {

try {

// perform database look-up for device’s IP address

dev.setIPAddress(DBLib.getDeviceIP(dev));

} catch (Exception e) {

// catching java.sql.SQLException & runtime exceptions

throw new ConfigureException(“device IP lookup failed”, e);

} try {

// rmi lookup of WSDPBridge

rmiWSDPBridge = (WSDPBridge) Registry.lookup(..);

rmiWSDPBridge.configure(dev, param, value);

} catch (Exception e) {

// catching RemoteException, WSDPBridgeException, & runtime

throw new ConfigureException(“configuration failed”, e);

} }

 

Here, you’re catching the various exceptions that can occur, and then stuffing them right into the newly created ConfigureException, nice and neat. Now you only have to throw one type of exception (plus the RemoteException) to represent all the things that can go wrong on this tier. Yet, you hold on to the lower-level exception, which will prove valuable, as you will see.

Looking at configureDevice(), it throws only the ConfigureException (and the RemoteException), yet it performs operations that throw three implementation-specific exceptions (not to mention the happy runtime exceptions). Instead of passing all these different exceptions up the stack (by declaring them in the throws clause of the method), you nest the lower-level exceptions inside the high-level ConfigureException, then throw the ConfigureException instead. This abstracts the details of the implementation from the caller (the client), which is good design, and resonates nicely with Java’s bent towards interface-driven architecture.

Now, let’s look at the WSDPBridge method configure(), called by the server method above. Note that WSDPBridgeException is a NestingException also.

public void configure(Device dev, String param, String value)
    throws RemoteException, WSDPBridgeException
{
    Socket s = null;
    try {
        // open a socket to the device
        s = new Socket(dev.getIPAddress(), WSDP_PORT);
    } catch (Exception e) {
        // catches java.io.IOException, runtime exceptions
        throw new WSDPBridgeException("device connect failed", e);
    }
    try {
        // protocol-specific method of configuring device
        negotiateConfiguration(s, dev, param, value);
    } catch (Exception e) {
        // catches InvalidConfigurationException & runtime exceptions
        throw new WSDPBridgeException("invalid configuration", e);
    } finally {
        // close the socket once we're done
        s.close();
    }
}

Now you can see that it’s possible to create a whole chain of nested exceptions. In this way, you explicitly define relationships of exceptions at each level or tier. In doing so, you automatically make it easy to determine when and where an exception’s inception occurred in the remote code.

Here is the definition of ConfigureException. All you need to do is extend the constructors of NestingException. Note that the WSDPBridgeException does exactly the same thing. Why do you define exceptions that do nothing except extend the NestingException? For the same reason that the Java Virtual Machine throws NullPointerExceptions instead of just Exceptions — the type of the exception tells you what went wrong.

public class ConfigureException extends NestingException {
    public ConfigureException() {}
    public ConfigureException(String msg) {
        super(msg);
    }
    public ConfigureException(Exception nestedException) {
        super(nestedException);
    }
    public ConfigureException(String msg, Exception nestedException){
        super(msg, nestedException);
    }
}

Displaying error messages

Getting back to the example, let’s say that everything worked, but you had a faulty configuration. You’ll have a chain of nested exceptions as in Figure 2:

Figure 2. A chain of nested exceptions

Now let’s go back to the client:

    try {
        rmiServer.configureDevice(device, cfg_param, cfg_value);
    } catch (ConfigureException e) {
        // handleError might pop up a dialog box
        handleError(e.getMessage());
        ...
    }

e.getMessage() would return the following string:

configuration failed: invalid configuration: invalid value for param 'blern'

Other error strings from other possible error conditions in this example are:

device IP lookup failed: Couldn't connect to database.
device IP lookup failed: Attempt to view non-existent device
configuration failed: device connect failed: no route to host
configuration failed: invalid configuration: array index out of bounds (1>=1)

The message is constructed tier by tier, level by level. Each exception adds its piece, then adds getMessage() of the nested exception. It’s automatic. This is what object-oriented design is all about.

Debugging

When debugging the application, the stack trace gives you the juiciest information, right on down to the line number where the error occurred. Accessing it is easy:

try {

rmiServer.configureDevice(device, cfg_param, cfg_value);

} catch (ConfigureException e) {

// handleError might pop up a dialog box

handleError(e.getMessage());

System.out.println(“Debug info follows:”);

System.out.println(e.getStackTraceString());

}

 

How’s this for remote debugging:

Debug info follows: com.flarn.InvalidConfigurationException: invalid value for param ‘blern’

at com.flarn.WSDPBridge.negotiateConfiguration(WSDPBridge.java:237)

at com.flarn.WSDPBridge.configure(WSDPBridge.java:57)

at com.flarn.WSDPBridge_Skel.dispatch(WSDPBridge_Skel.java:40)

at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:164)

at sun.rmi.transport.Transport.serviceCall(Transport.java:154)

at sun.rmi.transport.tcp.TCPTransport.handleMessages(Compiled Code)

at sun.rmi.transport.tcp.TCPTransport.run(Compiled Code)

at java.lang.Thread.run(Thread.java:466) ——– nested by: com.flarn.WSDPBridgeException: invalid configuration: invalid value for param ‘blern’

—> nested com.flarn.InvalidConfigurationException: invalid value for param ‘blern’

at com.bleen.CustomServer.configureDevice(CustomServer.java:138)

at com.bleen.CustomServer_Skel.dispatch(CustomServer_Skel.java:40)

at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:164)

at sun.rmi.transport.Transport.serviceCall(Transport.java:154)

at sun.rmi.transport.tcp.TCPTransport.handleMessages(Compiled Code)

at sun.rmi.transport.tcp.TCPTransport.run(Compiled Code)

at java.lang.Thread.run(Thread.java:466)

 

Converting your existing exceptions to nesting ones

It’s a simple procedure to turn your custom exceptions into nesting exceptions. All you need to do is insert the NestingException class somewhere at the base of your exception hierarchy, then add constructors to your custom exception classes so that they pass the nested exception up the superclass chain, like ConfigureException. If you’ve overridden any getMessage() methods, they need to behave like the getMessage() of NestingException, by appending the nestingException.getMessage(). You’ll do the same for the toString if you’ve overridden it.

The importance of a clean exception definition

To reap the benefits of this technique and the multitier architecture, it’s important to define specific exceptions for each tier or logical level of code. In this example, you created the ConfigureException to represent the spectrum of exceptions that could happen at the server tier, and the WSDPBridgeException to represent the spectrum of exceptions at the WSDPBridge level.

A good rule of thumb is that each remote method on a logically defined tier should declare that it throws just one exception (in addition to RemoteException): a nesting exception that nests any of the exceptions that could be thrown within that method’s implementation. To reiterate a thought expressed earlier, this methodology plays perfectly into interface-driven architecture: the implementation-specific exceptions are abstracted into a singly defined exception for some interface method.

Drawbacks and caveats

Any design implementation involves trade-offs, and this one is (ahem) no exception. There are a few things to consider to make sure this Java Tip is for you.

One is that for every component that adopts this technique, every remote method on that component must have at least one try/catch block, as lower-level exceptions get nested into higher levels. This can mean a lot of cut-and-paste code, which is often error-prone and less readable. In both remote methods in this example, there were two try/catch blocks. While it’s only necessary to have one try/catch block to achieve the nesting behavior and produce a remote stack trace, the more blocks you have, the more descriptive your error messages can be (at the expense of readability). Another problem is that any exceptions that could be nested would need to be in the classpath on the server. If a WSDPBridgeException were nested deeply in an exception chain and passed via RMI to the client, the client side would need to be able to construct (deserialize) the entire exception chain, which includes the WSDPBridgeException. Most likely, the WSDPBridgeException is not in the client’s classpath locally, which would result in a ClassNotFoundException. Fortunately, RMI provides a mechanism to load remote code. This involves a so-called “class server” in your application server that serves remote classes on a user-defined port using the HTTP protocol. This means that any classes that the client needs to resolve just need to be available via this class server. The Java RMI tutorial has detailed instructions on how to do this (see Resources).

Another limitation is that some systems are so dynamic that you don’t know which components are going to be on different tiers until runtime. For systems that are this dynamic, this approach can require an enormous amount of extra code in the form of try/catch blocks. In the worst case, you might have a system with hundreds of logical subsystems, each of which could be on different tiers. Using nested exceptions, any call between two of these subsystems requires a try/catch block on each end, just in case RMI was used. Most of the time the subsystems might be on the same tier and thus the try/catch blocks don’t do any useful work, but they still need to be in the code, just in case. This won’t greatly impact performance, but it will make the code much less readable.

I’m sure many of you are wondering how expensive it is to serialize the stack trace for not just one but possibly several exceptions in a nested chain, each with a stack trace. It certainly would be a lot more coming through the pipe. However, the trade-off here is very advantageous to you: better debugging information versus an extra tenth of a second for transmission — not too shabby. The savings in hours of debugging vastly outweighs the slight performance hit. Also, don’t forget that this technique deals with error conditions — events that aren’t supposed to occur under normal conditions. Exception handling as a programming construct was invented to make dealing with error conditions easier, and this technique certainly jibes with that intent.

One thing worth mentioning about this example is that, since it is a management application, it might be more aptly developed using the new Java Management Extensions (JMX) and/or the Java Dynamic Management Kit (JDMK) (see Resources). This example was not created to illustrate the most proper solution to the problem given, but to proffer a simple multitiered application that demonstrates the power of this technique.

Nevertheless, JMX and JDMK do use RMI, as does the J2EE Application Programming Model, plus all the technologies that model uses (JSP, servlets, JNDI, JMS, EJB, and so forth). The bottom line is that the nesting exception technique can be advantageously applied wherever you throw exceptions across remote boundaries.

Conclusion

Exception handling might seem to be an extraneous and annoying part of programming. Nothing could be further from the truth; a well-designed exception methodology can minimize the amount of error-handling code and make the real code easy to write and maintain. Due to the complexity of handling errors in a multitier environment, the NestingException technique is a powerful ally in the quest to take advantage of multitier architecture. Without its benefits, the enterprise programmer is doomed to waste many hours debugging remote errors while struggling to provide meaningful error messages.

After receiving a Commodore 64 computer at the tender age of 10, Terren Suydam dove into the world of computer nerd-dom, requiring glasses soon after. Programming pays the bills for him today, as a software engineer at 3NO Systems. More than that, programming for Terren serves as a means of self-expression, as he has used his talents to build a template engine for an online magazine, a fractal ray tracer, an animated fractal applet, and other brain candy. He has a healthy fascination with life, and an unhealthy fascination with trying to program it. Terren would like to thank Mark Roulo for providing the idea and code for capturing the remote stack trace.

Source: www.infoworld.com