The introduction of nested exceptions in JDK 1.4 meant that many kinds of software now rethrow exceptions as root causes, possibly several levels deep. While this has preserved debuggability for deeply nested problems, the stack trace format seems almost designed to confuse the reader. Taking the example from the Javadoc of Throwable.printStackTrace:
HighLevelException: MidLevelException: LowLevelException
at Junk.a(Junk.java:13)
at Junk.main(Junk.java:4)
Caused by: MidLevelException: LowLevelException
at Junk.c(Junk.java:23)
at Junk.b(Junk.java:17)
at Junk.a(Junk.java:11)
... 1 more
Caused by: LowLevelException
at Junk.e(Junk.java:30)
at Junk.d(Junk.java:27)
at Junk.c(Junk.java:21)
... 3 more
The "... 3 more" notation is tricky to interpret even after reading the Javadoc! It is an abbreviation for the raw list of causes:
HighLevelException: MidLevelException: LowLevelException
at Junk.a(Junk.java:13)
at Junk.main(Junk.java:4)
Caused by: MidLevelException: LowLevelException
at Junk.c(Junk.java:23)
at Junk.b(Junk.java:17)
at Junk.a(Junk.java:11)
at Junk.main(Junk.java:4)
Caused by: LowLevelException
at Junk.e(Junk.java:30)
at Junk.d(Junk.java:27)
at Junk.c(Junk.java:21)
at Junk.b(Junk.java:17)
at Junk.a(Junk.java:11)
at Junk.main(Junk.java:4)
The raw list, though longer, is actually easier to interpret because you can almost ignore all but the last stack trace, which gives the actual call sequence; this sequence is broken up in the current implementation so it is hard to follow. ("Almost" because the upper stack traces contain a bit more information: where the wrapping exception was thrown from. Usually this is uninteresting, but occasionally it matters.)
I suggest a more processed stack trace which actually shows the original call stack in logical order while preserving all the useful information of the current implementation and being a bit shorter to boot:
LowLevelException
at Junk.e(Junk.java:30)
at Junk.d(Junk.java:27)
at Junk.c(Junk.java:21)
Caused: MidLevelException: LowLevelException
at Junk.c(Junk.java:23)
at Junk.b(Junk.java:17)
at Junk.a(Junk.java:11)
Caused: HighLevelException: MidLevelException: LowLevelException
at Junk.a(Junk.java:13)
at Junk.main(Junk.java:4)
or even more concisely:
LowLevelException
at Junk.e(Junk.java:30)
at Junk.d(Junk.java:27)
at Junk.c(Junk.java:21)
Caused: MidLevelException
at Junk.c(Junk.java:23)
at Junk.b(Junk.java:17)
at Junk.a(Junk.java:11)
Caused: HighLevelException
at Junk.a(Junk.java:13)
at Junk.main(Junk.java:4)
I find this format much more intuitive. It looks nearly like the stack trace would have looked if there had not been exception wrappers:
LowLevelException
at Junk.e(Junk.java:30)
at Junk.d(Junk.java:27)
at Junk.c(Junk.java:21)
at Junk.b(Junk.java:17)
at Junk.a(Junk.java:11)
at Junk.main(Junk.java:4)
and it only introduces two extra lines of text for each wrapping, showing clearly both where the original exception was caught, and where the wrapper exception was thrown. The formatting matches the developer's experience that in most cases an exception cause in Java is just a workaround for the existence of checked exceptions, which need to be "translated" to different types, or occasionally annotated with a more descriptive message.
HighLevelException: MidLevelException: LowLevelException
at Junk.a(Junk.java:13)
at Junk.main(Junk.java:4)
Caused by: MidLevelException: LowLevelException
at Junk.c(Junk.java:23)
at Junk.b(Junk.java:17)
at Junk.a(Junk.java:11)
... 1 more
Caused by: LowLevelException
at Junk.e(Junk.java:30)
at Junk.d(Junk.java:27)
at Junk.c(Junk.java:21)
... 3 more
The "... 3 more" notation is tricky to interpret even after reading the Javadoc! It is an abbreviation for the raw list of causes:
HighLevelException: MidLevelException: LowLevelException
at Junk.a(Junk.java:13)
at Junk.main(Junk.java:4)
Caused by: MidLevelException: LowLevelException
at Junk.c(Junk.java:23)
at Junk.b(Junk.java:17)
at Junk.a(Junk.java:11)
at Junk.main(Junk.java:4)
Caused by: LowLevelException
at Junk.e(Junk.java:30)
at Junk.d(Junk.java:27)
at Junk.c(Junk.java:21)
at Junk.b(Junk.java:17)
at Junk.a(Junk.java:11)
at Junk.main(Junk.java:4)
The raw list, though longer, is actually easier to interpret because you can almost ignore all but the last stack trace, which gives the actual call sequence; this sequence is broken up in the current implementation so it is hard to follow. ("Almost" because the upper stack traces contain a bit more information: where the wrapping exception was thrown from. Usually this is uninteresting, but occasionally it matters.)
I suggest a more processed stack trace which actually shows the original call stack in logical order while preserving all the useful information of the current implementation and being a bit shorter to boot:
LowLevelException
at Junk.e(Junk.java:30)
at Junk.d(Junk.java:27)
at Junk.c(Junk.java:21)
Caused: MidLevelException: LowLevelException
at Junk.c(Junk.java:23)
at Junk.b(Junk.java:17)
at Junk.a(Junk.java:11)
Caused: HighLevelException: MidLevelException: LowLevelException
at Junk.a(Junk.java:13)
at Junk.main(Junk.java:4)
or even more concisely:
LowLevelException
at Junk.e(Junk.java:30)
at Junk.d(Junk.java:27)
at Junk.c(Junk.java:21)
Caused: MidLevelException
at Junk.c(Junk.java:23)
at Junk.b(Junk.java:17)
at Junk.a(Junk.java:11)
Caused: HighLevelException
at Junk.a(Junk.java:13)
at Junk.main(Junk.java:4)
I find this format much more intuitive. It looks nearly like the stack trace would have looked if there had not been exception wrappers:
LowLevelException
at Junk.e(Junk.java:30)
at Junk.d(Junk.java:27)
at Junk.c(Junk.java:21)
at Junk.b(Junk.java:17)
at Junk.a(Junk.java:11)
at Junk.main(Junk.java:4)
and it only introduces two extra lines of text for each wrapping, showing clearly both where the original exception was caught, and where the wrapper exception was thrown. The formatting matches the developer's experience that in most cases an exception cause in Java is just a workaround for the existence of checked exceptions, which need to be "translated" to different types, or occasionally annotated with a more descriptive message.