I do pretty much what Stan does, except I do have a few specific subclasses where I expect the caller to take action on the exception rather than let it bubble up to the controller to be logged.
For example, my DAO base class throws UniqueKeyViolatedException and ObjectNotFoundException but allows all other Hibernate exceptions (converted to unchecked exceptions by Spring) to propagate. These are both unchecked exceptions, by the way, since I also expect that sometimes the caller will consider the exception a logic error and not catch it.
At the root, there's a generic ServiceException as the base unchecked exception for wrapping other exceptions. It has the chaining features discussed above but not much else. AppException is the root checked exception, but I've been moving away from those due to their PITA factor.
One thing I do that I've seen most other people avoid (though I don't know why) is make use of
Java's built-in exceptions when they make sense, mostly IllegalArgument/StateException. I've never gotten a reason other than "that's just how we do it" from those that avoid them. Anyone else have a reason?