- Static binding of location to jump to. This is what goto, callcc, while, for etc. do.
- Dynamic binding of location to jump to. This is what exceptions do.
You can show that they are really different, in the sense that (in the absence of powerful language features), they cannot simulate each other, see [1]. Once you realise that exceptions and gotos are quite different thinking about error handling becomes more clearer.
[1] J. G. Riecke, H. Thielecke, Typed Exceptions and Continuations Cannot Macro-Express Each Other.
When a language support contructions such that normal control flow event are either "success + result", or "failure + error", it's much easier to follow, for me, and experience has taught me that it is far less error prone. Exceptions have a use for things that are unexpected, but happened anyway, such as OutOfMemoryException.
It's not goto's that were so painful, it was unstructured gotos. Calling exceptions gotos is not a reasonable criticism. They have some very well-defined structure, and additional benefits like separating your happy-path code from your error-handling code, making both simpler.