0

I'm not sure if this question was asked before, but I've been always wondering why every instance of Throwable, unless the fillInStackTrace method is overridden/suppressed, fills its stacktrace in the constructor while being instantiated by default. Consider the following code:

final class ThrowableTest {

    private ThrowableTest() {}

    public static void main(final String... args) throws Throwable { throw go(); }
    private static Throwable go() { return goDeep(); }
    private static Throwable goDeep() { return goDeeper(); }
    private static Throwable goDeeper() { return goEvenDeeper(); }
    private static Throwable goEvenDeeper() { return new Throwable(); }

}

Executing the code above would produce the following output:

Exception in thread "main" java.lang.Throwable
    at ThrowableTest.goEvenDeeper(scratch.java:9)
    at ThrowableTest.goDeeper(scratch.java:8)
    at ThrowableTest.goDeep(scratch.java:7)
    at ThrowableTest.go(scratch.java:6)
    at ThrowableTest.main(scratch.java:5)

I would find the following hypothetical stacktrace more intuitive (assuming the fillInStackTrace method is not overridden and the Throwable constructor does not invoke it in such a "special" JRE):

Exception in thread "main" java.lang.Throwable
    at ThrowableTest.main(scratch.java:5)

I'm wondering: why is the stacktrace filled in the constructor (in its simplest scenario) rather than at the exception throwing site using throw? If I'm not mistaken, there are some scenarios when fillInStackTrace can be used to configure the exception to be (re)thrown, and the throw as described would re-write the stacktrace to the current one. But if so, the throw statement (and the athrow instruction) might check if the stacktrace of the exception is set to null before filling it up on its own (assuming that throwing something other than an instance of Throwable is undefined as it's described in the specification). What was the design choice behind it?

  • "more intuitive"? really? would make finding the error exceptionally(!) hard! And Exceptions are not meant to be returned, but to be created where the exception comes from... – user85421 Oct 17 '19 at 13:14
  • @CarlosHeuberger Yes, more intuitive because it marks the point _where_ the exception is thrown rather than the point where it's instantiated. It don't care the latter and I'm not sure why the constructor suffers from the `fillInStackTrace` penalty. Suppose you have a factory method that instantiates your domain logic exceptions, say `throw ObjectNotFoundException.create(SomeType.FOO, barId)` which can be a method that delegates instantiation elsewhere -- this is what I meant by returning an exception through the entire method chain. – terrorrussia-keeps-killing Oct 17 '19 at 13:23
  • @CarlosHeuberger, at times, exceptions must play service or flow control roles, and in those cases it's quite crucial to *not* fill the stack trace in - which is why the method is public and overridable. There's also quite a strong argument for abstracting building of the exception, including swapping its type on condition. This means that while generally exceptions are thrown from the same line they are created on, this is not the only use case. – M. Prokhorov Oct 17 '19 at 13:24
  • 1
    What was the design choice behind it is a question you should ask to those who made those choices. But it's relatively hard to "fill in" things in an object when it doesn't exist yet and if the "filling in" is made an inevitable part of the compiled code generated by there being a throw, then it's difficult to avoid the filling in, which is a perfectly reasonable and perfectly feasible option the way things are now. – Erwin Smout Oct 17 '19 at 13:25
  • @fluffy, the proper "why" answer requires getting to the bottom of the brain of a person who designed these classes more than twenty years ago, so time travel. As for capturing stack trace from the place where you throw, you can always do `throw (MyException) myException.fillInStackTrace()`, which discards the original stack trace and captures a new one. – M. Prokhorov Oct 17 '19 at 13:27
  • @M.Prokhorov Yeah, exactly, most of Java developers just use the "throw new ...;` pattern. – terrorrussia-keeps-killing Oct 17 '19 at 13:28
  • @M.Prokhorov if you need something special, then you have to implement it. The standard is there to cover the most common used use cases! – user85421 Oct 17 '19 at 13:32
  • @fluffy maybe I'm using too much Java, because for me that would not be more intuitive. – user85421 Oct 17 '19 at 13:34
  • @fluffy, one thing I can think of regarding the "why" is currently getting ST is tightly coupled with `Exception` creation. For example, `Thread.current.getStackTrace()` is essentially the `return new Exception().getStackTrace()`, and changing this requires noticeable refactoring, to introduce a third entity into this whole thing. Doing something with ST in `throw` directive also messes with "rethrow" pattern - i.e. when you can't deal with exception yourself, rethrowing it would still trim out parts of original ST. – M. Prokhorov Oct 17 '19 at 13:35
  • I see very little reason for using Factory patterns on Exception objects. Your factory method must declare a return type which is some common supertype of all of the possible Exception object types it can factor, and that's the also the Exception type your method is going to be required to declare in its throws clause. You ***can*** add specific subtypes in that throws clause, but nothing [in the language] guarantees completeness or accuracy of that information. – Erwin Smout Oct 17 '19 at 13:38
  • @M.Prokhorov That's an interesting point, thank you. What if `Throwable`, back to those days, would have a slightly modified public API declaring a method something like `public final native boolean isThrown();` returning `true` if and only if the throwable is already thrown, so that `athrow` might do additional work if necessary? Would this work with the rethrow pattern? – terrorrussia-keeps-killing Oct 17 '19 at 13:54
  • 1
    @fluffy, possibly it would work, but this complicates the system for what is indeed essentially a corner case, because you'd then have to track all exceptions that at some point were thrown somehow. So from user point they'd be a one-off object, but JVM has to keep them around on the offchance someone got a hold of one and saved it somewhere. – M. Prokhorov Oct 17 '19 at 13:58
  • Consequently [it is not usually safe to store and reuse exception objects](https://stackoverflow.com/questions/15090664/is-it-safe-to-store-an-instance-of-an-exception-and-reuse-it). – Raedwald Oct 17 '19 at 14:19
  • See also [How much work should be done in a constructor?](https://stackoverflow.com/questions/293967/how-much-work-should-be-done-in-a-constructor). – Raedwald Oct 17 '19 at 14:34

0 Answers0