Exception::Class is very good, but you probably want to check the more recent Throwable::Error.
It is based on Moose, has a Moose-based interface which makes it nicer to handle for Moose users.
And it allows roles compositions for exceptions, which can make a significant difference. For example, an exception can compose several exception roles : it can "do" both File::Exception and Scheduler::Exception.
It is also very easy to learn if you use Moose.
One thing that Exception::Class has and that Throwable::Error doesn't, is an exception factory that builds all the class desired from a structure describing the exception class hierarchy. But you will probably not need it if you use Throwable::X and its tagging system - yes; check out Throwable::X too !
Sorry if this feels more like a shameless plug than a review ... if you do not like to have to repeat the names of the exception classes to set up the hierarchy you might like Exception::Class::Nested. It's just a simple wrapper of this module that lets you describe the hierarchy by nesting the class definitions. This may prevent rather hard to debug typos :-)
Exception::Class is a great CPAN distribution. I've been using Error.pm for a while (and now co-maintain it), and was looking for something less magical (or even "black-magicke") and less of a leaky abstraction, and Exception-Class turns out to be a great solution.
The syntax for defining many exception classes is great, and it is also provides a convenient syntax for trapping and analyzing the excepions, which does not use closure games like Error.pm does.
In short: don't use Error.pm - and use Exception-Class. I should note that there's search.cpan.org/dist/Try-Tiny/ (and some other Method-Signature-like hacks) which may be worth to take a look as an alternative to the "use Error qw(:try);" syntax, but Exception-Class is naturally a complement.
It's Perlish! It's sophisticated exceptions! It's not a contradiction!
Like so much Rolskyware, Exception::Class provides a well-engineered and very customizable system for doing something that most large software needs: error trapping. It has an optimal interface (especially with the addition of Foo->caught), a simple and Perlish way to define exception hierarchies, and reasonable default behavior. Use it.