So, the question is in the title.
The only one thought that comes in mind why we need to introduce custom Exception class is to pass additional info with exception raising.
Any additional reasons?
There are several reasons why you may want to create custom exceptions:
Add custom logic to an exception, to simplify the interaction with the exception itself.
ActiveRecord::RecordInvalid is an example: the message is built from the actual record errors, and a reference to the record itself is stored in the exception.
Differentiate errors to improve error handling. Having different error types allows you to specifically rescue only certain types of errors, and not all the errors. You can also have different rescue blocks depending on the error type.
Re-raising typed errors. This is very important for libraries that internally relies on other libraries. A good practice of software composition is that library A uses B internally, whatever error B raises should be wrapped into an A error to make sure that the users of A don't have to deal with B directly.
In general, providing custom errors enhances the error handling experience. A good resource to learn more about errors is Avdi's book Exceptional Ruby. Highly recommended.