"Exceptions are for exceptional conditions" is a meme that I wish would just die. Its origin lies in 1990s C++ compilers, which were extremely inefficient when dispatching exceptions, leading programmers, as a pragmatic measure, to use error codes for "expected" errors and exceptions only for cases thought to occur infrequently.
We've long since past the time that we have to worry about such concerns. "Exceptions for exceptional errors" just means that you have to write code that both cares about exception safety and propagates error codes from subroutines. It's the worst of both worlds.
It's not elegant - it means that every single exception raised by any function you call, or any function they might call, and so forth, is now part of your API. If you're an HTTPS library, and you're using some OpenSSL bindings for certificate validation, OpenSSLCertificateValidationError is part of your API because callers are now catching that. If you switch to BoringSSL or NSS or whatever, callers won't be expecting BoringSSLCertificateValidationError or NSSMismatchedCertsException.
Java has a particularly inelegant and ugly solution here involving declaring what types of exceptions might be thrown. But that still doesn't change the fact that changing that list is an API change, it just makes it more explicit.
So if you care about API stability (and I firmly believe that no solution that ignores API stability is "elegant" - it is at best "cute"), you're basically required to catch the vast majority of exceptions your own dependencies generate and translate them to your own exception types. You'll need to make a MyHTTPLibCertificateValidationError, unwrap the contents of OpenSSLValidationError, and put them in the new object, or you can never switch away from OpenSSL without an API break. And you want your dependencies to follow the same discipline.
At that point, as I said above, why use unwinding? None of the exceptions in your program can safely pass more than one level of the call stack at a time; each level has to explicitly approve raising it another level or wrap the exception in its own type (or handle it). The only ones that can really unwind are standard library ones like OutOfMemoryError that are expected to go all the way up the call stack to the top of the program or at best the top of the current request, print or otherwise log a backtrace, and abort the entire thing in progress - i.e., exceptional conditions. Exceptions for expected conditions are a different thing entirely, precisely because you don't want unwinding, you want step-by-step propagation.
This has nothing to do with efficiency. This has to do with correctness and robustness.
We've long since past the time that we have to worry about such concerns. "Exceptions for exceptional errors" just means that you have to write code that both cares about exception safety and propagates error codes from subroutines. It's the worst of both worlds.
Just use exceptions for all errors. It's elegant.