JavaData & JavaProcessing Stack Trace Data in Java

Processing Stack Trace Data in Java

Developer.com content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

Java Programming Notes # 1784


Preface


The recently released JavaTM 2 SDK, Standard Edition Version 1.4 contains a number of new features.  This article explains how
to use some of those new features to get, interpret, and process stack
trace data.

Prior to release 1.4, stack trace information was available only in
the printed format produced by the printStackTrace method. 
With the advent of the StackTraceElement class, and the getStackTrace
method of the Throwable class, stack trace information is now available
through programmatic access.

As a result, you can write logic into your programs to analyze and make
decisions on the basis of stack trace information.  In addition, you
can encapsulate that information into objects.  This makes it possible
for you to serialize and write stack trace data to a disk file, send it
out over a network, or perform any other action that is appropriate for
information encapsulated in an object.

Viewing tip

You may find it useful to open another copy of this lesson in a separate
browser window.  That will make it easier for you to scroll back and
forth among the different listings and figures while you are reading about
them.

Supplementary material

I recommend that you also study the other lessons in my extensive collection
of online Java tutorials.  You will find those lessons published at
Gamelan.com
However, as of the date of this writing, Gamelan doesn’t maintain a consolidated
index of my Java tutorial lessons, and sometimes they are difficult to
locate there.  You will find a consolidated index at
www.DickBaldwin.com.

Discussion
and Sample Code


I’m going to jump right into a sample program that illustrates the programmatic
handling of stack trace data without a lot of preliminary discussion. 
Along the way, I will also illustrate Java’s new cause or exception
chaining
facility.  (An earlier article entitled
Chained
Exceptions in Java
discusses this facility in detail.)

The program named StackTr01

A complete listing of the program named StackTr01 is provided
in Listing 21 near the end of the lesson.  I will discuss the program
in fragments.  However, before getting into the fragments, I will
provide an overview of the operation of the program.

Five classes

The program consists of the following five classes:

  • StackTr01
  • Class01
  • NewEx01
  • NewEx02
  • NewEx03

The controlling class is named StackTr01.  Most of the work
gets done in the class named Class01.

There are three new exception classes named NewEx01, NewEx02,
and NewEx03.  These classes are used to instantiate exception
objects, which are thrown by the methods in Class01.  Each
of these three classes extends Exception.

The main method of the controlling class

The controlling class consists solely of the main method. 
Code in the main method instantiates an object of type Class01
and invokes a method named meth01 on that object’s reference.

Methods in Class01

The class named Class01 defines the following methods:

  • meth01
  • meth02
  • meth03
  • meth04
  • encapsulate – encapsulate stack trace data in a Vector object
  • writeSer – write a serialized Vector object to a disk file
  • readSer – read a serialized Vector object from a disk file
  • display – display the stack trace data in a Vector object

The first four methods in the list are used to create a chain of exceptions. 
The remaining four methods in the list provide the behavior indicated in
the text.

Method invocation sequence

As mentioned above, the method named meth01 is invoked by the
main
method in the controlling class.

The method named meth01 invokes meth02.  The method
named meth02 invokes meth03, and the method named meth03
invokes meth04.  Each of these method invocations is contained
in a try block, followed by a catch block of type
Exception.

Throw an exception

The method named meth04 at the end of the invocation chain throws
an exception of type NewEx03.  This exception is caught by
the catch block in meth03.

The code in the catch block in meth03 encapsulates the
incoming object’s reference as the cause in a new exception object
of type
NewEx02, and throws that new exception object.

Similarly, the exception thrown by meth03 is caught by meth02
The code in the catch block of meth02 encapsulates its incoming
exception as the cause in a new exception object of type NewEx01
and throws it.  As you can see, this code creates a chain of exceptions
as control moves back up the call stack.

Here is where things get interesting

Finally, the exception thrown by meth02 is caught by meth01
Most of the interesting code is contained in the catch block for
meth01,
which is designed to illustrate the programmatic handling of stack trace
data.

Print the stack trace

The catch block in meth01 receives an incoming exception
parameter of type NewEx01.  The printStackTrace method
is invoked on that object’s reference to give us a baseline for comparison.

Encapsulate stack trace data in a Vector object

Then the stack trace data is extracted from the incoming exception object
and encapsulated in an object of type Vector.  The Vector
object is serialized and written to a disk file to illustrate one form
of programmatic handling of stack trace data.  This Vector
object contains the entire chain of exceptions, and the stack trace data
associated with each exception in the chain.

Read and display the serialized stack tract
data

Then the serialized data is read from the disk file, and a Vector
object containing the original stack trace data is reconstructed. 
The reconstructed object is passed to a custom print method, which extracts
the information from the Vector object and displays it in a format
similar to the standard format produced by the printStackTrace method.

Encapsulating stack trace data in an object

These operations illustrate that you now have the ability to extract
stack trace data and encapsulate it in an object.  Then, you can do
just about anything with the stack trace data that you can do with any
other kind of data encapsulated in an object.  As a result, stack
trace data is now available for programmatic access and handling.

The custom exception classes

Listing 1 shows the three custom exception classes.

 

class NewEx01 extends Exception{
  public NewEx01(String message,
                  Throwable throwable){
    super(message, throwable);}
}//end NewEx01
//===================================//
class NewEx02 extends Exception{
  public NewEx02(String message,
                  Throwable throwable){
    super(message, throwable);}
}//end NewEx02
//===================================//
class NewEx03 extends Exception{
  public NewEx03(String message){
    super(message);}
}//end NewEx03

Listing 1

Overloaded constructors

Some comments are in order regarding these exception class definitions. 
Subsequent to release 1.4, whenever you define a new exception class,
you should provide four overloaded constructors having argument lists that
match the following four constructors of the Throwable class:

  • Throwable()
  • Throwable(String message)
  • Throwable(String message, Throwable cause)
  • Throwable(Throwable cause)

By making the argument lists of your constructors match those of the Throwable
class, objects instantiated from your new classes will be capable of supporting
the new cause or chained exception facility in release 1.4.

Did not provide four overloaded constructors

However, for purposes of this sample program, I knew that I would not
need all four constructors in each class.  For brevity, I included
only the constructor that I would need in each of the new exception classes.

(Note that the argument list for NewEx03 is different
from the argument lists for the other two.  That is because the program
does not encapsulate a cause when it instantiates and throws an object
of type NewEx03.)

Pass parameters to superclass constructor

The code in each of the constructors is very simple.  In each case,
the parameters received by the constructor are passed to the constructor
for the superclass.  This causes the parameter information to be saved
in instance variables of the Throwable class, and makes it possible
later to successfully invoke the methods of the Throwable class
on objects instantiated from these classes.

Traversing the call stack

The overview that I provided earlier explains that there is a sequence
of method calls, which place several methods on the stack.  In other
words, a chain of method calls is created on the runtime stack.

Then a method at the end of the chain named meth04 throws an
exception.  That exception is caught by the method named meth03,
which was the method that called meth04.  The method named
meth03
encapsulates the incoming exception as a cause in another exception
of a different type and throws the new exception.

Each time a new exception is thrown, one method is popped off the stack. 
This catch and throw process continues until a method near the top of the
stack, named meth01, provides code to handle the exception without
throwing a new exception.

Begin discussion where the exception is thrown

I’m going to begin the discussion with the method named meth04
that throws the first exception.  Then I will continue back through
the call stack discussing each method in sequence.

Listing 2 shows meth04, which throws the first exception. 
This triggers the chain of exceptions being thrown as control progresses
back through the call stack.

 

  void meth04() throws NewEx03{
    throw new NewEx03(
                 "Thrown from meth04");
  }//end meth04

Listing 2

The method named meth04 in Listing 2 constructs and throws an
exception object of type NewEx03.  Note that this exception
object does not encapsulate a cause.

Catch and throw

The exception object of type NewEx03 is caught by the catch
block of type Exception in meth03, shown in Listing 3.

 

  void meth03() throws NewEx02{
    try{
      meth04();
    }catch(Exception e){      
      throw new NewEx02(
               "Thrown from meth03",e);
    }//end catch
  }//end meth03

Listing 3

The most important thing to note about Listing 3 is the instantiation
and throwing of a new exception object of type NewEx02.  This
code uses a constructor for NewEx02 that encapsulates the incoming object reference
of type NewEx03 as the cause in the new object.

(Note that the declared type of the catch block in
meth03
is Exception.  This catch block can catch objects instantiated
from the class Exception or any of its subclasses. NewEx03
is a subclass of Exception.)

Catch and throw again

The exception object thrown by meth03 in Listing 3 is caught
by the method named meth02 in Listing 4.

 

  void meth02() throws NewEx01{
    try{
      meth03();
    }catch(Exception e){
      throw new NewEx01(
               "Thrown from meth02",e);
    }//end catch
  }//end meth02

Listing 4

The code in listing 4 is very similar to that in Listing 3.  The
code in meth02 instantiates and throws a new object of type NewEx01,
with the incoming object of type NewEx02 encapsulated as the cause
in that object.

Handling the exception

The exception thrown by meth02 in Listing 4 is caught and processed
by the method named meth01, which begins in Listing 5.  The
code in meth01 does not instantiate and throw a new exception object. 
Rather, it completely handles the incoming exception in its catch
block, thus breaking the chain of exceptions.

 

  void meth01(){
    try{
      meth02();//call meth02
    }catch(Exception e){
      System.out.println(
                   "Print StackTrace");
      e.printStackTrace();

Listing 5

Print the stack trace

The code in Listing 5 begins by invoking the printStackTrace
method on the incoming parameter to cause the stack trace to be printed
in the standard format on the standard error stream.  The output is
shown in Figure 1 (we will need this later for comparison purposes).

 

Print StackTrace
NewEx01: Thrown from meth02
 at Class01.meth02(StackTr01.java:92)
 at Class01.meth01(StackTr01.java:60)
 at StackTr01.main(StackTr01.java:52)
Caused by: NewEx02: Thrown from meth03
 at Class01.meth03(StackTr01.java:102)
 at Class01.meth02(StackTr01.java:85)
 ... 2 more
Caused by: NewEx03: Thrown from meth04
 at Class01.meth04(StackTr01.java:112)
 at Class01.meth03(StackTr01.java:100)
 ... 3 more

Figure 1

The print format

A word about the format of the data in Figure 1 may be in order. 
Those lines that read … 2 more and … 3 more indicate
that the specified number of lines were omitted for brevity.  As far
as I know, this is the standard output format of the printStackTrace
method over which we have no control.  (We will see later that
these lines were not omitted from my custom print format for the same stack
tract data.)

Encapsulate stack trace data in a Vector object

Continuing with the catch block in meth01, the statement in Listing 6 invokes the
encapsulate
method to cause the stack trace data to be extracted from the incoming
exception object and encapsulated in a Vector object.

 

      Vector vecOuter = encapsulate(e);

Listing 6

The encapsulate method

At this point, I’m going to set the method named meth01 aside
for the moment and discuss the method named encapsulate, (which
is a method of my own design).
  The beginning of the encapsulate
method is shown in Listing 7.  This is a rather long method, so I
will discuss it in fragments.  Before getting into the details of
the method, however, I will provide some background information.

 

  Vector encapsulate(Throwable e){
    Vector vecOuter = new Vector();
    Throwable cause = e;

Listing 7

Contents of the incoming Throwable object

The encapsulate method receives a reference to a Throwable
object as an incoming parameter.  In this case, it is the Throwable
object of type NewEx01 caught by the method named meth01.

The end of a chain of exceptions

Recall that this Throwable object was the result of a chain of
operations involving methods catching objects of one type, encapsulating
the incoming object as the cause in an object of another type, and
then throwing the object of the new type.

Encapsulates the history of the call stack

As a result, this incoming object contains a lot of information about
the history of the call stack from the point where the original exception
was thrown to the end of the chain of exceptions.  In other words,
the incoming Throwable object contains a Throwable cause,
which
contains a Throwable cause, which contains a Throwable
cause
, etc.

We will process the incoming Throwable object in order to extract
all of that information.

What does a Throwable object contain?

According to Sun:

“A throwable contains a snapshot of the execution stack
of its thread at the time it was created. It can also contain a message
string that gives more information about the error. Finally, it can contain
a cause: another throwable that caused this throwable to get thrown. The
cause facility is new in release 1.4. It is also known as the chained exception
facility, as the cause can, itself, have a cause, and so on, leading to
a “chain” of exceptions, each caused by another.”

Assumes a chain of exceptions

The procedure that I will use assumes that at each point along the sequence
of exceptions being caught and thrown, a reference to the incoming Throwable
object was encapsulated as a cause in the new object.

(This is what constitutes a chain of exceptions.  If
the chain is broken by a failure of a catch block somewhere along
the way to encapsulate its incoming exception as a cause, neither the printStackTrace
method, nor this procedure can obtain information about what happened beyond
the break in the chain.)

No cause in the original object

The original object that was thrown did not encapsulate a Throwable
as a cause.  Therefore, when we reach the point where the cause
encapsulated in the Throwable object equals null, we will
conclude that we have reached the end of the chain.

Save stack trace data along the way

This algorithm will drill down, getting and processing Throwable
(cause
) objects, until it reaches the point where the
getCause
method returns null.  At each step along the way, it will get
and save the stack trace information encapsulated in that particular Throwable
object.  Each set of stack trace data will be saved in a Vector
object.

Save the Vector objects in a Vector object

The set of Vector objects so produced will be saved in another
Vector
object.  Thus, we will end up with a Vector object containing
references to other
Vector objects.  Each of the secondary
Vector
objects will contain the stack trace data encapsulated in one of the Throwable
objects encountered along the way.

Create the outer Vector object

The code in Listing 7 instantiates a new Vector object and saves
it in a reference variable named vecOuter.  This Vector
object will be populated with references to other Vector objects
as the algorithm progresses.

Save incoming reference in variable named cause

The code in Listing 7 also saves the incoming reference in a reference
variable having the descriptive name cause.  This reference
variable is used in the conditional clause of the while loop that
follows.

Begin a while loop

Listing 8 shows the beginning of a while loop.  This loop
controls the process of drilling down to the point where a Throwable
(cause
) object is encountered with a cause value of null
(the
end of the chain of exceptions).

 

    while(cause != null){
      StackTraceElement[] trace 
               = cause.getStackTrace();

Listing 8

Get and save array of StackTraceElement objects

The code in Listing 8 invokes the getStackTrace method to get
and save an array of references to StackTraceElement objects. 
These objects represent the stack trace information encapsulated in the
Throwable(cause)
object being processed during this iteration of the while loop.

What is a StackTraceElement object?

Here is what Sun has to say about an object of type StackTraceElement:

“An element in a stack trace … Each element represents
a single stack frame. All stack frames except for the one at the top of
the stack represent a method invocation. The frame at the top of the stack
represents the execution point at which the stack trace was generated.
Typically, this is the point at which the throwable corresponding to the
stack trace was created.”

Thus, each StackTraceElement in the array represents one stack frame,
and the collection of StackTraceElement objects in the array represents
the entire stack trace.

We will be able to correlate the information in each StackTraceElement
object with source code line numbers when we examine the stack trace data
later.

So, now we have an array of StackTraceElement
objects

At this point, we have an array object containing references to StackTraceElement
objects.  Each StackTraceElement object represents one stack
frame, beginning at the point where the exception represented by this Throwable
object was thrown, and ending at the bottom of the stack.

Encapsulate the data in a Vector

For convenient handling later, we will encapsulate the information in
this array object in a Vector object, and encapsulate that Vector
object, along with other Vector objects in another Vector
object.

In addition, we will also encapsulate the type of the Throwable
object, along with the message encapsulated in the Throwable object
in our Vector object.

The code in Listing 9 instantiates a new Vector object to contain
the data.

 

      Vector vec = new Vector();

      vec.add("Cause" 
                   + cause.toString());

Listing 9

Type of Throwable and message

The code in Listing 9 also invokes the toString method on the
Throwable
object and concatenates the resulting string with the word “Cause”. 
The string that results from the concatenation is added to in the Vector
object.  We will use the word “Cause” later to identify the
string containing the type of the Throwable object along with the
message encapsulated in the
Throwable object.

“The getMessage method is also available to get the
message.  However, in this case, the toString method was preferable
because it returned both the type of the object and the text of the message.”

Get and save information about each stack frame

Each element in the array of StackTraceElement objects contains
information about one frame of the stack.  The following four methods
of the StackTraceElement class are available to access four different
pieces of information describing each stack frame:

  • getClassName – Returns a String, which is the name of the
    class containing the execution point represented by this stack trace element.
  • getMethodName – Returns a String, which is the name of the
    method containing the execution point represented by this stack trace element.
  • getFileName – Returns a String, which is the name of the
    source file containing the execution point represented by this stack trace
    element.
  • getLineNumber – Returns an int, which is the line number
    of the source line containing the execution point represented by this stack
    trace element.

Iterate on the array object with a for loop

The for loop in Listing 10 iterates on the array object, extracting
and saving each of the four pieces of information for each element in the
array (for each frame in the stack).

 

      for(int i=0;i<trace.length;i++){
        vec.add("Class" + trace[i].
                       getClassName());
        vec.add("Method" + trace[i].
                      getMethodName());
        vec.add("File" + trace[i].
                        getFileName());
        vec.add("Line" + trace[i].
                      getLineNumber());
      }//end for loop

Listing 10

Concatenate with a descriptive word

For convenient handling later on, each of the four pieces of information
is concatenated to a word that describes the type of information.

(Note that this concatenation process causes the int
line number value to be converted to a string.)

Contents of the Vector object

Each of the four strings (each containing information about a single
stack frame)
is added to the Vector object during each iteration
of the for loop.

Thus, when the entire array object has been processed, the Vector
object contains one String object describing the type of Throwable
being processed and the message encapsulated in that Throwable object. 
In addition, the Vector object contains four String objects
representing each stack frame that existed at the point in time that the
exception was thrown.

(For example, if the stack contained three frames, the Vector
object would contain thirteen elements, four elements representing each
stack frame plus one element representing the Throwable object.)

Add the populated inner Vector to the outer Vector

This Vector object is now fully populated.  The single statement
in Listing 11 adds it to the outer Vector object that serves
as a container for the individual Vector objects being produced
by this process.  (Note that the statement in Listing 11 is outside
the for loop.)


 

      vecOuter.add(vec);

Listing 11

Get the encapsulated Throwable and do it again

Each Throwable object may encapsulate another throwable object
known as the cause.

One Throwable object is processed during each iteration of the
current while loop.

The code in Listing 12 invokes the getCause method on the current
Throwable
object to get a reference to the Throwable object
(if any)
that it encapsulates.  If this Throwable object doesn’t encapsulate
another throwable object, the getCause method returns null.

 

      cause = cause.getCause();      
    }//end while loop

    return vecOuter;
  }//end encapsulate

Listing 12

Go back to the top of the while loop

Then control returns to the top of the while loop where the new
value of cause is tested for null.  If the value of
cause
is not null, (meaning that there is another Throwable
object to be processed),
another iteration of the while loop
is executed to process that Throwable object.

If the value of cause is null, control exits the while
loop, causing the return statement in Listing 12 to be executed. 
This return statement returns a reference to the outer Vector object,
which has been populated with Vector objects.  Each of the
Vector
objects contained in the outer Vector object contains stack trace
information encapsulated by one of the Throwable objects involved
in the series of chained exceptions.

Back to meth01

The return statement returns control to the method named meth01,
as shown in Listing 13.

 

//continuing in meth01
      writeSer(vecOuter,"junk");
      Vector vecIn = 
               (Vector)readSer("junk");

Listing 13

Stack trace data is encapsulated in a Vector
object

At this point, the full set of stack trace data for the set of chained
exceptions has been encapsulated in an object of the class Vector
Once we have the stack trace data in this form, we can do whatever we want
with it.  For example, we could serialize the Vector object
and write it in a disk file or send it across the network.

Demonstrate object serialization

The code in Listing 13 demonstrates this by first serializing the Vector
object to a disk file, and then reading that disk file and reconstructing
the Vector object in its original state.  This is accomplished
by sequentially invoking two methods that I wrote named writeSer
and readSer.

Once again, I will set the method named meth01 aside temporarily
and discuss the two methods mentioned above.

The writeSer method

The method named writeSer is shown in Listing 14.  This
method will serialize an incoming object and write the byte stream produced
by serialization into a file whose name is specified by an incoming parameter
of type String.  I’m not going to explain this code in detail
(I
have previously published lessons on object serialization on my
web
site
).


 

  void writeSer(Object obj, 
                          String name){
    try{//to serialize the Vector obj
      ObjectOutputStream  outStream  =
               new  ObjectOutputStream(
                  new FileOutputStream(
                                name));
      outStream.writeObject(obj);
      outStream.flush();
      outStream.close();
    }catch(Exception excp){
      System.out.println(excp);
    }//end catch
  }//end writeSer

Listing 14

The key statement in Listing 14 is the invocation of the writeObject
method, which performs the serialization of the Vector object and
causes the byte stream to be written into a disk file.

The readSer method

The method named readSer, shown in Listing 15, will read the
file containing the serialized object, reconstruct the original object,
and return a reference to the reconstructed object as type Object.

 

  Object readSer(String name){
    try{
      ObjectInputStream inStream = 
                 new ObjectInputStream(
                   new FileInputStream(
                                name));
      return inStream.readObject();
       
    }catch(Exception excp){
      System.out.println(excp);
    }//end catch
    //required to satisfy compiler
    return null;
  }//end readSer

Listing 15

The key statement in Listing 15 is the invocation of the readObject
method.  This method performs the reconstruction of the original Vector
object using a stream of bytes as input.

Back to meth01 again

The return statement in Listing 15 returns control to meth01,
as shown in Listing 16.  Listing 16 shows the remainder of meth01.

 

//continuing in meth01
      display(vecIn);
    }//end catch    
  }//end meth01

Listing 16

Display the stack trace data

The code in Listing 16 invokes a method named display that I
wrote to display the stack trace data encapsulated in the Vector
object.  This data is displayed in a custom format of my own design.

Obviously, at this point, you could display the stack trace data in
any format that you choose.  I chose to display it in a format that
resembles the standard format produced by the printStackTrace method. 
This makes it easy to confirm the validity of the stack trace data encapsulated
in the Vector object by comparing the two displays.

The display method

The display method begins in Listing 17.

 

  void display(Vector vecOuter){
    Enumeration enumOuter = 
                   vecOuter.elements();
    while(enumOuter.hasMoreElements()){
      Vector vecInner = (Vector)
               enumOuter.nextElement();

Listing 17

Nested Vector objects

Recall that the stack trace data is now contained in nested Vector
objects.  That is to say, one Vector object serves as an outer
container for several other Vector objects.  Each of the inner
Vector
objects contains stack trace information pertaining to one of the Throwable
objects produced by the chain of exceptions and encapsulated in the final
Throwable
object.

Nested enumerations

I needed to display the data encapsulated in each of the inner Vector
objects.  This led to a solution based on nested enumerations. 
An outer loop enumerates on the outer Vector object, to extract
each of the inner vector objects in sequence.

An inner loop enumerates on each of the inner Vector objects
to extract, format, and display the data encapsulated in each of the inner
Vector
objects.

I don’t plan to discuss enumeration in detail.  I have previously
published tutorial lessons explaining the enumeration process on my
web
site
.

The outer enumeration loop

The code in Listing 17 sets up the outer enumerator loop.  This
loop gets a reference to one element contained in the outer Vector
object during each iteration of the loop.  These elements are also
Vector
objects, and each of them is referred to as vecInner.

The Vector object accessed during each iteration of the outer
loop contains String data describing stack trace data originally
encapsulated in a Throwable object (in the chain of throwable
objects).

The inner enumeration loop

The code in Listing 18 sets up an inner enumeration loop.  This
loop gets a reference to one String element contained in the inner
Vector
object during each iteration of the loop.

 

      Enumeration enumInner = 
                   vecInner.elements();
      while(enumInner.
                    hasMoreElements()){
        String str = (String)
               enumInner.nextElement();

Listing 18

String identifiers

Recall that each of the String objects in the Vector object
begins with one of the following words:

  • Cause
  • Class
  • Method
  • File
  • Line

There should be only one String object beginning with the word Cause
in each Vector object.  Then there should be one String
object beginning with each of the other four words for each frame that
existed on the stack at the point in time that the Throwable object
was constructed and thrown.

A decision tree

Although it looks complicated, the code in Listing 19 is simply a big
decision tree that tests the String object to determine which of
the five types of data it represents.  Then the code in Listing 19
formats the data appropriately and displays it on the screen.

 

        if(str.startsWith("Cause")){
          System.out.print(
                 str.substring("Cause".
                            length()));
        }else if(str.startsWith(
                             "Class")){
          System.out.println();
          System.out.print("  " 
               + str.substring("Class".
                            length()));
        }else if(str.startsWith(
                            "Method")){
          System.out.print("." 
              + str.substring("Method".
                            length()));
        }else if(str.startsWith(
                              "File")){
          System.out.print("(" 
                + str.substring("File".
                            length()));
        }else if(str.startsWith(
                              "Line")){
          System.out.print(":" 
                + str.substring("Line".
                      length()) + ")");
        }//end else

Listing 19

An example output

For example, the first five passes through the inner enumeration loop
on an individual Vector object might produce something like that
shown in Figure 2.

 

NewEx01: Thrown from meth02
  Class01.meth02(StackTr01.java:92)

Figure 2

The first line in Figure 2 results from the String object that
begins with the word Cause.  The second line is a composite
of the information extracted from four String objects beginning
with the words Class, Method, File, and Line.

What does the output represent?

The first line of output represents the type of one Throwable
object, and the message encapsulated in that object.

The second line of output represents one stack frame that existed at
the point in time that the Throwable object was created and thrown. 
If the stack contained more than one frame, other lines similar to the
second line would be produced, one for each stack frame.

Wrap up the display method

Listing 20 shown the remaining code in the display method, including
the ends of the inner and outer enumeration loops.

 

      }//end inner while loop
      System.out.println();//blank line
    }//end outer while loop
  }//end display

Listing 20

The full output

Figure 3 shows the full output produced by the display method
in this program.

 

NewEx01: Thrown from meth02
  Class01.meth02(StackTr01.java:92)
  Class01.meth01(StackTr01.java:60)
  StackTr01.main(StackTr01.java:52)
NewEx02: Thrown from meth03
  Class01.meth03(StackTr01.java:102)
  Class01.meth02(StackTr01.java:85)
  Class01.meth01(StackTr01.java:60)
  StackTr01.main(StackTr01.java:52)
NewEx03: Thrown from meth04
  Class01.meth04(StackTr01.java:112)
  Class01.meth03(StackTr01.java:100)
  Class01.meth02(StackTr01.java:85)
  Class01.meth01(StackTr01.java:60)
  StackTr01.main(StackTr01.java:52)

Figure 3

Throwable objects in the chain of exceptions

From this output, we can see that three separate Throwable objects
were thrown in the chain of exceptions.  The first object that was
thrown and the state of the stack at the time it was created and thrown
are identified by the boldface text at the bottom of Figure 3.

As shown by the top boldface line of text, the first object that was
thrown was of type NewEx03.  The message reads: “Thrown
from meth04”.

Five frames on the stack

There were five frames on the stack at the time the object was thrown. 
According to Sun:

“All stack frames except for the one at the top of the stack
represent a method invocation. The frame at the top of the stack represents
the execution point at which the stack trace was generated. Typically,
this is the point at which the throwable corresponding to the stack trace
was created.”

The frame at the bottom of the stack represents the main method
of the StackTr01 class.

The frame at the top of the stack represents the method named meth04
of the class named Class01.  This is the method that threw
the first exception.  The line number of 112 “represents
the execution point at which the stack trace was generated.”

When was the Throwable object created?

The stack trace data indicates that the object was created at line 112
in the source code for the program.  Lines 112 and 113 in the source
code read as shown in Figure 4.

 

112 throw new NewEx03(
113             "Thrown from meth04");

Figure 4

(Although the statement that created the Throwable
object is spread across two lines, it begins on line 112.)

Method invocations

As indicated in the earlier quotation from Sun, the remaining line numbers
in the boldface text represent method invocations in the methods and classes
indicated.

For example, the statements corresponding to the line numbers in the
last four lines in Figure 3 are shown in Figure 5.  As you can see,
each of these statements is a method invocation.

 

100  meth04();//in meth03
 85  meth03();//in meth02
 60  meth02();//in meth01
 52  new Class01().meth01();//in main

Figure 5

Compare with printed stack trace data

At this point, I recommend that you compare Figure 3 with Figure 1. 
Figure 1 shows the output from the printStackTrace method for this
program.  Figure 3 shows my programmatic formatting of stack trace
data for the same program.  Except for the fact that some of the data
is missing in Figure 1, you should be able to match up each of the data
elements in Figure 3 with the corresponding data elements in Figure 1.

Programmatic handling of stack trace data

Now you know how to get stack trace information and encapsulate it in
an object suitable for processing under program control.  You know
how to serialize that data so that it can be written in a disk file or
transmitted across a network.  You know how to read the serialized
byte stream and reconstruct the original object containing the stack trace
data.

Equally important, you also know how to interpret the stack trace data
that is available in this fashion.  Obviously, you can’t write code
to process the stack trace data, and make decisions on the basis of that
data, unless you know how to interpret it.

Run the Program


If you haven’t already done so, I encourage you to copy the code from Listing
21 into your text editor, compile it, and execute it.  Experiment
with it, making changes, and observing the results of your changes.

Remember, however, that you must be running Java version 1.4 or later
to compile and execute this program.

Complete Program Listing


A complete listing of the program discussed in this lesson is shown in
Listing 21.

 

/*File StackTr01.java  
Copyright 2002, R. G. Baldwin
Illustrates programmatic handling of
stack trace data for the case where
the stack trace includes causal 
information.

Tested using JDK 1.4.0 under Win2000

The output produced by the program is
similar to the following:

Print StackTrace
NewEx01: Thrown from meth02
 at Class01.meth02(StackTr01.java:92)
 at Class01.meth01(StackTr01.java:60)
 at StackTr01.main(StackTr01.java:52)
Caused by: NewEx02: Thrown from meth03
 at Class01.meth03(StackTr01.java:102)
 at Class01.meth02(StackTr01.java:85)
 ... 2 more
Caused by: NewEx03: Thrown from meth04
 at Class01.meth04(StackTr01.java:112)
 at Class01.meth03(StackTr01.java:100)
 ... 3 more
NewEx01: Thrown from meth02
  Class01.meth02(StackTr01.java:92)
  Class01.meth01(StackTr01.java:60)
  StackTr01.main(StackTr01.java:52)
NewEx02: Thrown from meth03
  Class01.meth03(StackTr01.java:102)
  Class01.meth02(StackTr01.java:85)
  Class01.meth01(StackTr01.java:60)
  StackTr01.main(StackTr01.java:52)
NewEx03: Thrown from meth04
  Class01.meth04(StackTr01.java:112)
  Class01.meth03(StackTr01.java:100)
  Class01.meth02(StackTr01.java:85)
  Class01.meth01(StackTr01.java:60)
  StackTr01.main(StackTr01.java:52)
**************************************/

import java.io.*;
import java.util.*;
import java.util.logging.*;

class StackTr01{
  public static void main(
                        String[] args){
      new Class01().meth01();
  }//end main
}//end StackTr01
//===================================//

class Class01{
  void meth01(){
    try{
      meth02();//call meth02
    }catch(Exception e){
      System.out.println(
                   "Print StackTrace");
      e.printStackTrace();
      //Encapsulate stack information
      // in a Vector
      Vector vecOuter = encapsulate(e);
      
      //Write the Vector to disk as a
      // serialized object for demo
      writeSer(vecOuter,"junk");
      //Read the serialized data and
      // reconstruct the Vector object
      Vector vecIn = 
               (Vector)readSer("junk");
      //Display the stack trace data
      // in the Vector object
      display(vecIn);
    }//end catch    
  }//end meth01
  //---------------------------------//
  
  void meth02() throws NewEx01{
    try{
      meth03();
    }catch(Exception e){
      //Construct and throw a new
      // exception object with the
      // exception caught by this
      // method encapsulated as the
      // cause
      throw new NewEx01(
               "Thrown from meth02",e);
    }//end catch
  }//end meth02
  //---------------------------------//
  
  void meth03() throws NewEx02{
    try{
      meth04();
    }catch(Exception e){      
      throw new NewEx02(
               "Thrown from meth03",e);
    }//end catch
  }//end meth03
  //---------------------------------//
  
  void meth04() throws NewEx03{
    //Construct and unconditionally
    // throw a new exception object
    // with no encapsulated cause
    throw new NewEx03(
                 "Thrown from meth04");
  }//end meth04
  //---------------------------------//
  
  //Method to encapsulate stack 
  // information in a Vector 
  // containing refs to other Vector 
  // objects
  Vector encapsulate(Throwable e){
    Vector vecOuter = new Vector();
    //Treat the incoming Throwable
    // as a cause
    Throwable cause = e;
    //Drill down to the point where
    // there is no cause encapsulated
    // in the cause
    while(cause != null){
      //Get the StackTraceElement for
      // this cause
      StackTraceElement[] trace 
               = cause.getStackTrace();
      //Create a Vector to contain
      // data from this
      // StackTraceElement
      Vector vec = new Vector();
      //Include high-level information
      // about this cause in the vector
      vec.add("Cause" 
                   + cause.toString());
      //Loop, get, and save four pieces
      // of data for each item in the
      // StackTraceElememt.  Each piece
      // of data is saved as a String
      // with an identifier prepended.
      for(int i=0;i<trace.length;i++){
        vec.add("Class" + trace[i].

                       getClassName());
        vec.add("Method" + trace[i].
                      getMethodName());
        vec.add("File" + trace[i].
                        getFileName());
        vec.add("Line" + trace[i].
                      getLineNumber());
      }//end for loop
      //Add this Vector object to the
      // outer enclosing vector
      vecOuter.add(vec);
      //Continue drilling down. Get the
      // cause encapsulated in this
      // cause and start over.  Exit
      // the loop when getCause returns
      // null
      cause = cause.getCause();      
    }//end while loop
    return vecOuter;
  }//end encapsulate
  //---------------------------------//
  
  //Method to serialize the Vector and
  // write it to a disk file.
  void writeSer(Object obj, 
                          String name){
    try{//to serialize the Vector obj
      ObjectOutputStream  outStream  =
               new  ObjectOutputStream(
                  new FileOutputStream(
                                name));
      outStream.writeObject(obj);
      outStream.flush();
      outStream.close();
    }catch(Exception excp){
      System.out.println(excp);
    }//end catch
  }//end writeSer
  //---------------------------------//
  
  //Method to read the serialized data,
  // reconstruct, and return the Vector
  // object
  Object readSer(String name){
    try{
      ObjectInputStream inStream = 
                 new ObjectInputStream(
                   new FileInputStream(
                                name));
      return inStream.readObject();
       
    }catch(Exception excp){
      System.out.println(excp);
    }//end catch
    //required to satisfy compiler
    return null;
  }//end readSer
  //---------------------------------//
  
  //Method to display the stack trace
  // data encapsulated in the Vector
  // in a specific format
  void display(Vector vecOuter){
    Enumeration enumOuter = 
                   vecOuter.elements();
    while(enumOuter.hasMoreElements()){
      Vector vecInner = (Vector)
               enumOuter.nextElement();
      Enumeration enumInner = 
                   vecInner.elements();
      while(enumInner.
                    hasMoreElements()){
        String str = (String)
               enumInner.nextElement();
        if(str.startsWith("Cause")){
          System.out.print(
                 str.substring("Cause".
                            length()));
        }else if(str.startsWith(
                             "Class")){
          System.out.println();
          System.out.print("  " 
               + str.substring("Class".
                            length()));
        }else if(str.startsWith(
                            "Method")){
          System.out.print("." 
              + str.substring("Method".
                            length()));
        }else if(str.startsWith(
                              "File")){
          System.out.print("(" 
                + str.substring("File".
                            length()));
        }else if(str.startsWith(
                              "Line")){
          System.out.print(":" 
                + str.substring("Line".
                      length()) + ")");
        }//end else
      }//end while loop
      System.out.println();
    }//end while loop
  }//end display
  //---------------------------------//
}//end Class01
//===================================//

//Note:  For brevity, I included only
// the required constructors in these
// new exception classes.
//This is a new exception class
class NewEx01 extends Exception{
  public NewEx01(String message,
                  Throwable throwable){
    super(message, throwable);
  }
}//end NewEx01
//===================================//

//This is a new exception class
class NewEx02 extends Exception{
  public NewEx02(String message,
                 Throwable throwable){
    super(message, throwable);
  }
}//end NewEx02
//===================================//

//This is a new exception class
class NewEx03 extends Exception{
  public NewEx03(String message){
    super(message);
  }
}//end NewEx03

Listing 21

Copyright 2002, Richard G. Baldwin.  Reproduction in whole or in
part in any form or medium without express written permission from Richard
Baldwin is prohibited.

About the author

Richard G. Baldwin
is a college professor (at Austin Community College in Austin, TX) and
private consultant whose primary focus is a combination of Java, C#, and
XML. In addition to the many platform and/or language independent benefits
of Java and C# applications, he believes that a combination of Java, C#,
and XML will become the primary driving force in the delivery of structured
information on the Web.

Richard has participated in numerous consulting projects, and he
frequently provides onsite training at the high-tech companies located
in and around Austin, Texas.  He is the author of Baldwin’s Programming
Tutorials,
which has gained a worldwide following among experienced and aspiring programmers.
He has also published articles in JavaPro magazine.

Richard holds an MSEE degree from Southern Methodist University and
has many years of experience in the application of computer technology
to real-world problems.

baldwin@DickBaldwin.com

-end-

 

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories