Exceptions work excellently for business-level server software; typically they make the server transaction all or nothing, with transactional semantics. The stateless nature of HTTP, and transactions provided by the database, usually add up to make this easy. Business logic throws an exception, everything unwinds, the transaction rolls back, no state was modified, and the server returns an error code to the client: all good.
When you need partial updates to state in the case of an error, then you're in trouble. When you have multiple transactions to coordinate, but no distributed transaction coordinator, you're in trouble (but you are anyway whether you use exceptions or not). The more state you need to preserve in an error case, the worse exceptions are for your use case. Ideally, exceptions are never caught except at the topmost loop: server request dispatcher, UI event handler, or command line driver. When execution must continue with recovery, exceptions are an inferior tool.