views:

429

answers:

7

If I run the following test, it fails:

public class CrazyExceptions {
    private Exception exception;

    @Before
    public void setUp(){
        exception = new Exception();
    }

    @Test
    public void stackTraceMentionsTheLocationWhereTheExceptionWasThrown(){
        String thisMethod = new Exception().getStackTrace()[0].getMethodName();
        try {
            throw exception;
        }
        catch(Exception e) {
            assertEquals(thisMethod, e.getStackTrace()[0].getMethodName());
        }
    }
}

With the following error:

Expected :stackTraceMentionsTheLocationWhereTheExceptionWasThrown
Actual   :setUp

The stack trace is just flat out lying.

Why isn't the stack trace rewritten when the exception is thrown? I am not a Java dev, and maybe I'm missing something here.

+1  A: 

Because you didn't ask that that stack trace be rewritten. It was set when you created it in the setUp method, and you never did anything to alter it.

The Exception class doesn't give you any opportunity to set the method name; it's immutable. So there's no way that I know of where you could re-set the method name, unless you wanted to resort to something heinous like reflection.

Your @Test annotation doesn't tell me if you're using JUnit or TestNG, because I can't see the static import, but in either case you can run a test to see if a particular exception is thrown by using the "expected" member in the @Test annotation.

duffymo
I'm using jUnit, but I wasn't trying to test for exceptions being thrown. I used the test just to illustrate my question.
Martinho Fernandes
I'm not sure what the question is: "How do I alter the method from a stack trace?"
duffymo
A: 

I think the assumption is that you won't be instantiating an exception unless you are in the process of throwing it, so why pay the price to get the stack trace twice?

It would be difficult to recreate the stack trace while throwing it, as that is just sending the object out.

The exception should be fully setup before the throw, so part of the instantiation is to get the stack trace.

UPDATE:

You can call fillInStackTrace to resolve this: http://java.sun.com/j2se/1.4.2/docs/api/java/lang/Throwable.html#fillInStackTrace%28%29

James Black
+10  A: 

The stack trace is created when the exception is instantiated, not when it is thrown. This is specified behaviour of the Java Language Specification

20.22.1  public Throwable()

This constructor initializes a newly created Throwable object with null as
its error message string. Also, the method fillInStackTrace (§20.22.5) is
called for this object. 

....

20.22.5  public Throwable fillInStackTrace()

This method records within this Throwable object information about the
current state of the stack frames for the current thread.

I don't know why they did it that way, but if the specification defines it like that, it is at least consistent on all the various Java VMs.

However, you can refresh it by calling exception.fillInStackTrace() manually.

Also note that you should use Thread.currentThread().getStackTrace() instead of using new Exception().getStackTrace() (bad style).

mhaller
I didn't use `Thread.currentThread().getStackTrace()` because `Thread.currentThread().getStackTrace()[0].getMethodName` is always `getStacktrace`...
Martinho Fernandes
+1 for pointing out the manual `fillInStackTrace` thing. In C# we get that behavior by default and we can simply `throw;` when we need to keep the stack trace when rethrowing.
Martinho Fernandes
It's `getStackTrace()[1]`, nothing more.
mhaller
@mhaller, no `[1]` doesn't get you the current method name in JDK 1.5, it is [2] (there is an extra internal method call). So getStackTrace is great to get a view of the stack, lousy if you care where you are (who says it won't change in another version to 3 or back to 1).
Yishai
+3  A: 

The stacktrace of the exception is filled in at creation time of the exception. Otherwise it would be impossible to catch an exception, handle it and rethrow it. The original stacktrace would get lost.

If you want to force this you have to call exception.fillInStackTrace() explicitly.

tangens
In C# we get that behavior by default and we can simply `throw;` when we need to keep the stack trace when rethrowing. That's why I got confused.
Martinho Fernandes
-1: We know from .NET that this is not the case. There is certainly some rationale for when the stack trace is captured, but it's not because it can't be managed when captured by a `throw e;` statement.
280Z28
@280Z28, the point is that in java there would be no way, as it has no such language construct as throw;.
Yishai
@Yishai: right, but keep in mind we're talking about the language design itself, so *if* this was the problem, they could have gotten around it. They might have called it `rethrow`, but whatever it became it's obvious they would need the ability to rethrow the exception without changing the stack trace.
280Z28
@280Z28, the question wasn't is there some way around it by changing the language, but why java doesn't set the stacktrace at the time it was thrown. It doesn't because it would have no mechanism to rethrow and keep the old stack trace. Instead it has fillInStackTrace if you need to reset the stack trace. It is a different design.
Yishai
+1  A: 

You wouldn't want throwing an exception to alter the stack track or you couldn't re-throw an exception safely.

public void throwsException() {
    throw new RuntimeException();
}

public void logsException() {
    try {
        throwsException();
    } catch (RuntimeException e) {
        e.printStrackTrace();
        throw e; // doesn't alter the exception.
    }
}

@Test
public void youCanSeeTheCauseOfAnException(){
    try {
        logsException();
    } catch(Exception e) {
        e.printStrackTrace(); // shows you the case of the exception, not where it was last re-thrown.
    }
}
Peter Lawrey
A: 

The stack trace in the exception corresponds to the "new" operation, nothing else.

Thorbjørn Ravn Andersen
A: 

I can see why they did it that way.

An exception occurs there and then and that is the exception. The exception is not an object you can fiddle with, reuse, and adjust (though people tried to, in order to "optimise" what is supposed to be an exceptional situation anyway). There is no exception until the exception actually happens and then it's a very special thing that occurs in a very special context that can now be reported and logged properly. By doing what you've done up there you're saying there's an exception in your setup method. Then, why don't you throw it there?

If I were to change the Java spec I'd fail compilation if an exception is instantiated but not thrown.

George