It's rare because RPC & filesystem access is usually wrapped in a library or module which does not have any business knowledge of the task at hand. A more realistic example is your getUser() call which calls 10 stack frames down to a filesystem access, and let's say you get a filesystem error. The natural thing to do is throw an exception which goes up all 10 stack frames and gets caught by getUser(), which provides some sort of "sorry, internal error" message to the client.
The notion that typing "if error return error" through 10 stack frames makes you think more clearly is absurd. Decades of C experience has shown that lazy programmers will ignore critical error conditions and introduce hard-to-find bugs because execution plows ahead past the original error.
Whether getUser() returns an error object or throws an exception is a question of high-level API design. Sometimes UserNotExistsException makes sense, sometimes a null (or error) result makes sense. That is an entirely separate issue. Any designer with significant experience will use both approaches as appropriate.
Again, I'm not disagreeing that exceptions tend to be more terse. Exceptions optimize for writability at the expense of readability. Reading linear code that uses if statements and loops is easier than code that uses try-catches. Especially trying to come up with all the ways control could jump from happy path code to error-handling code.
You tend not to be writing all 10 methods in a particular call chain at the same time. You will be writing a few methods that call each other inside a module. You paint this as a massive timesink, and I can assure you, it definitely is not.
Lazy programmers can also have catch-all exception handlers. I don't see how exceptions help make lazy programmers perform due diligence.
That being said, there is a place for exceptions. Truly exceptional conditions such as index out of bounds, or nil pointer dereference, or some internal precondition violated, should be treated in an exceptional manner. Go does this with panics, and panics are almost never caught as part of control-flow. They tend to be caught at the root of goroutines, logged, and the goroutine killed. The HTTP library, for instance, will catch panics in any goroutine it spawns, and write a 503.
I just find it odd that people treat commonplace things as exceptional. File open failed? Could not resolve hostname? Broken TCP connection? These aren't particularly exceptional things. They are probably not a result of a bug, and so should be handled by the programmer.
We seem to be going around in circles here... You say 10 layers of if error return error is not a time sink, and I say it is. I spent about a decade doing C and C++ programming before Java, and IMHO exception handling is second only to garbage collection as life-changing language improvements.
There is a key difference here: When a lazy C or Go programmer fails to check an error value, execution continues - possibly many lines or stack frames ahead before some sort of failure symptom is observable. In perverse cases this can produce silent data corruption. I spent far too much of the 90s chasing down these kinds of problems.
When a lazy programmer uses a catch-all exception handler, the error is still caught - immediately - with the full stack trace of the original problem. This is golden. Furthermore, a catch-all exception handler that prints an error message to the user/http request/whatever is often exactly the right approach.
There's a lot of stupidity in the Java standard libraries, but your examples (file failure, bad hostname, broken connection) are exactly the kinds of things that should be exceptions, and are usually best caught at a high level where meaningful errors can be reported to the user.
The notion that typing "if error return error" through 10 stack frames makes you think more clearly is absurd. Decades of C experience has shown that lazy programmers will ignore critical error conditions and introduce hard-to-find bugs because execution plows ahead past the original error.
Whether getUser() returns an error object or throws an exception is a question of high-level API design. Sometimes UserNotExistsException makes sense, sometimes a null (or error) result makes sense. That is an entirely separate issue. Any designer with significant experience will use both approaches as appropriate.