I don't like seeing the same 5 stack frames in the same class appear three separate times in a single stack trace. Because what does that stack trace say?
It says it took Foo five function calls -on itself- to figure out it's not responsible for an operation. So Foo asks Bar to do it, and after half a dozen more function calls someone asks Foo all over again. Who takes 5 function calls to figure out it's STILL not responsible for this action. Lather, Rinse, Repeat.
And how do you set a breakpoint in that call tree? Which time do you want to debug it? How many more times was it called before the exception was thrown?
We call the first framework function, which calls the second function with the default values for the arguments that were optional. The second function looks up the application's method using Reflection. Because Reflection code is verbose and has a lot of tricky cases, we want to share code between all places where we call it, so it delegates calling the API in 4 different methods.
Then come up the ServletFilters in an HTTP application, which is an example of pluggable system. If we declare 10 filters, they all do something like check the authentication, gzip the response, check the XSRF token, transform a WebApplicationException into a 404/500 reaponse. Unfortunately they all appear as "ServletFilters" in the stacktrace, until they delegate to the final HTTPServlet itself. Another example of pluggable system is OSGi: Each method call is wrapped in 5 function calls if the method is in another classloader.
There's only one improvement I'm able to imagine: Java could define a pluggable system at the language level, so we don't have to implement them using function calls. This pluggable system stretches for all delegation needs, from OSGi to ServletFilters to apps where the list of delegates were defined by a REST api, etc. Sounds like a big JSR...
The thing to remember is that when Java was young, the pool of people who grasped OOAD was very small. Nearly all of us were making it up as we went along. So Design Patterns came out and everyone glommed onto that book, and then went out and wrote code so awful and convoluted that it's now a joke.
J2EE was built up in that space, on a foundation of bad assumptions and unfounded theories. I was simply amazed that the same company that brought us the Eight Fallacies of Distributed Computing brought us the J2EE 1.0 spec. I was so appalled at its quality that I switched to UI development until well after the 2.0 spec was out and implemented.
It says something profound that every vendor that had any succes with those versions of the spec did it by defying the spec and supporting local dispatch. A lot of people couldn't scale their app past two machines, but load balancer eventually came around and fixed that.
Anyway, my point is you have a bunch of APIs written by newly minted experts, many of the foundational ones by people with absolutely no awareness of the physics of distributed computing, it's going to take a lot to spackle to cover those sins. Some did, others doubled down, but overall every problem was solved by another layer of indirection.
When refactoring came to the fore I hoped the industry would hastily reverse course. Spring was supposed to fix a lot of this but from my view, Spring became the thing is sought to replace. Full of cryptic levers and dials and massive indirection.
I think it was Mike Feathers who attributed this to cowardice. People afraid to make decisions put all the decisions behind a layer or two of indirection so they can "cheaply" change their minds later. But again we are back to Meyer and Liskov here.
If A then B (where A is the decision and B the action) allows you to change A without adding much if anything to B, or add to B without changing A. If this shows up in an inheritance hierarchy then you need about half as many overridden methods for the same call tree. Which means a shallower call tree, and code you can speak out loud.
I will agree with vbezhenar that Java stack traces are extremely useful. When something has gone wrong with an application in production, it is useful to know the call stack that led to the problem. I know that I didn't just fail in the method `Foo.doFoo()`, I failed in that method which was called by `Baz.doBaz()`. I know these two pieces of code are interacting which is a big clue as to the problem.
vbezhenar also offers good advice that logs of production software should be empty of exceptions and stack traces -- that the presence of anything is the sign of a problem, and thus rare in well-maintained software. Honestly, well-operating production software shouldn't emit any logs at all. Metrics and activity records if you need them, sure, but there's no need for logs. Logs can consequently be used to quickly pinpoint unexpected things happening.
a bit too verbose, but the amount of info I can get from UAT/PROD envs in case of an issue is mostly enough to fix the bug. verbosity can be solved easily by proper rolling & backup of logs, that's not rocket science.
I champion Sentry wherever I go, as it has an outstanding "fold these common explosions into one event" behavior, in addition to managing the _lifecycle_ of an exception/error, from first encounter through resolved in release, to the dreaded reoccurence.
I do keep a terminal open for my curiosity but I'm much happier with computers watching logs than me having to
It gets all the required information (source, stack and variable state), through a native JVM agent that transmits everything directly to the tool.
Of course if someone doesn't care about exceptions, it's just a bad style. Exception in the production log is like red light and alarm sound, BUG-BUG-BUG. I saw projects, which throw stacktrace after stacktrace, gigabytes of stacktraces (and then archived those teralogs and noone ever watched them). But those projects have much more problems, verbose logs is the least of them.
Yes, with heavy framework usage, especially when those frameworks generate proxy classes and wrappers and god knows what, it's often 2-3 lines of useful information between 90 lines of not-so-useful library or even autogenerated code. But only developer can judge it, environment should preserve anything.