As an example of useful exceptions: malloc errors. In C, handling malloc failures is a nightmare. So much so that very few programs actually do it properly. The reason for that is that not only do you need to check the return code of malloc, you need to check the return code of every single function in your entire program that ever calls a malloc anywhere down the line.
So while this piece of code might be nice if you see it in one or two places:
int rc = fx (); if (rc != 0) { handle_error (); }
Properly handling malloc errors means every single function call becomes a four line monstrosity, basically blowing up your code base by factor four and making the code much more difficult to read. Not to mention how insanely error prone it is to have to check the return code of every single function. When doing the wrong thing is so much easier than doing the correct thing, most people will do the wrong thing.
Panic on OOM is perfectly reasonable for most.
And yes, panics shouldn't be catchable. That's the whole point.
I have no problem with checked exceptions. Unchecked exceptions is another story, and forces you to “catch all” everywhere (most libraries don’t document every single possible exception of every single method).
In retrospect I don’t even know why everyone responded to my original post about focusing on software error handling as if I was attacking exceptions. I think unchecked exceptions and unchecked null pointers are bad, but that’s about it; and that’s not even what my top post was about.
Personally, I like the idea of Java's "checked" exceptions, particularly when your software involves lots of layers that need to use one-another appropriately, including adapting to error situations.
If you go "quick and dirty" with unchecked exceptions, you have the option to change them to checked ones later. When you do that a lot of stuff will "break", but really that will just be the compiler walking you through all the paths the exception could take, asking you to make a decision at each spot about what you want to do. (Pass the exception up the stack unchanged, catch it and wrap it and throw the wrapper, catch and recover, catch and ignore, etc.)
The reason exceptions tend to be overused is because the line between error and exception is blurry -- and it's blurry precisely because an 'exception' is really just a subset of errors. Unfortunately, most languages do not treat exceptions as a subset of an error, but as a completely disjoint/orthogonal thing, and that's the problem!
If we transitioned to languages that handle these concepts in a unified way (and ones that don't allow unchecked exceptions), this isn't a problem at all, and we can all happily write much more inherently reliable software.
Exceptions are great for exceptional stuff we really could not have expected (or just don't want to deal with so we want to cleanup nicely), but they tend to be overused for "anything that is not the expected result.
On the other hand, deferring non expected path to later using exception like you clearly state sometimes actually is good thing as it speeds up development time significantly. Depending on the project, you may actually really not care, and having that power in your hand in invaluable.