Adam Burley
Adam Burley

Reputation: 6079

How to get a correctly-chained stack trace for exceptions thrown when handling other exceptions?

Suppose I am handling FooException and BarException occurs. Let's assume they are both unchecked exceptions.

What I want to see in the stacktrace is:

com.bar.BarException: Bar Message
    at com.baz.BazCode(BazCode.java:123)
    ...
Caused by: com.foo.FooException: Foo Message
    at com.baz.BazCode(BazCode.java:321)
    ....
Caused by: ...

However, by default, all record of FooException will be erased from the stacktrace. For example:

// In a class written by me
/**
  * ...
  * @throws FooException if foo happens
  * @throws BarException if bar happens
  */
public void upperFrame() {
    try {
        foo.doSomething();
    } catch (FooException foo) {
        bar.doSomethingElse();
    }
}

// In class Bar (not written by me)
public void doSomethingElse() {
    if (someConditionWhichHappensToBeTrueInThisScenario()) {
        throw new BarException("Hello Bar World"); // At this point, FooException gets erased from the stack trace
    }
}

If BarException has a (message, cause) constructor then I can follow a rather crude kind of "manual cloning" process to achieve my goal:

try {
    foo.doSomething();
} catch (FooException foo) {
    try {
        bar.doSomethingElse();
    } catch (BarException bar) {
        BarException bar2 = new BarException(bar.getMessage(), foo);
        bar2.setStackTrace(bar.getStackTrace());
        throw bar2;
    }
}

However, if BarException does not have such a constructor (for example ClassCastException) then I am reduced to doing things like this:

try {
    foo.doSomething();
} catch (FooException foo) {
    try {
        bar.doSomethingElse();
    } catch (BarException bar) {
        RuntimeException e = new RuntimeException("com.bar.BarException: " + bar.getMessage(), foo);
        e.setStackTrace(bar.getStackTrace());
        throw e;
    }
}

This is dangerous because e has the wrong type and so may not be correctly handled by higher frames.

Is there a "best-practice" way to handle this situation?

Upvotes: 5

Views: 1038

Answers (2)

kg_sYy
kg_sYy

Reputation: 1215

As long as you pass the original exception to the new one as an argument, it creates the "caused by" chain and the stack trace is preserved. Your use cases seem a bit strange. To me, the exception when recovering or handling the error with more than some logging is another error and not really "caused by" the other error. I would just log the "foo" and throw the "bar".

In some cases I guess your way could make sense. To do this, you could pass the "foo" on as doSomethingElse(foo) and throw new BarException(foo) when something goes wrong handling this. Pretty much all standard exceptions support this constructor, and if you need to make your own just create a constructor that delegates to these.

I personally would not use them the way you seem to though. I use them to throw a different type of exception than the one I caught, if I want a different type for some reason. For example, to translate to exceptions of specific type to my application, or to translate checked to unchecked in cases where that makes sense. In such cases it is still good to keep the original exception and the full "caused by.." trace..

Upvotes: 1

sp00m
sp00m

Reputation: 48837

One solution is to use the Throwable#initCause(Throwable) method:

bar.initCause(foo);

Upvotes: 4

Related Questions