In my experience (I am a Java developer for 10+ years now) it is bad practice. Not that it is a code smell, but it most often hinders you in your daily work.
Think of an application that tries to read a file and probably throws an
FileNotReadableException. After a while you realize, that the file may not be readable due to insufficient access-rights. What do you do? Change the name of the exception - no, it may be still in use. Add a subtype
FileNotReadableBecauseOfAccessRightsException? Well, this is rather 2 errors - not readable + access rights, but the root cause is the rights thing. So you add an
InsufficientAccessRightsException somewhere else in the class tree. When doing so you also need to change the code where the original exception was caught since its 2 different types now (a violation of the open-closed-principle). And so on... And what's the gain? ... in contrast to an exception with a message? Static type safety. In the context of exceptions it is only useful to prevent typos. A developer could still use a wrong exception type (e.g.
EntryNotFoundException instead of
As said by dwoz: an exception is just a class.
My experience is that a few exceptions are sufficient. What you want to express are error-messages - for several messages you only need 1 (runtime) exception with a String field. All exceptions should be caught by a default error-handler at the highest possible layer (or earlier if necessary).
Besides the usual message, you sometimes need to store more information, like a filesystem-path, a username, a device-id, a uuid to more easily trace the error in a distributed system, etc. for those purposes I suggest to create custom exception classes.
As said by amon, a rule of thumb should be: for libraries use (some) exceptions, for (web-) applications us as few as possible