In a nutshell, in Java, there are RuntimeExceptions, CheckedExceptions and Errors.
Errors are fatal occurrences you can’t handle – like an java.lang.OutOfMemoryError. You should do everything possible to code in such a way that it never happens, however, if it does, there is nothing you can do to prevent the system from crashing – so never ever handle Errors.
CheckedExceptions are Exceptions that extend
java.lang.Exception. Use them for Exceptions that can and should be handled, especially for Business Exceptions, but use them sparsely, and handle them as early as possible. Only forward an Exception to the calling method / the caller, if it can actually handle the Exception in any meaningful way.
3. For Exceptions that cannot be properly handled, e.g. I/O Errors, use RuntimeExceptions, so your code won’t be cluttered with “throws MyRuntimeException” method signatures. Handle the exception on the very top of your system (e.g. by logging the exception) and stopping the system gracefully, or if possible, skip the failed item and continue with the next one. Many I/O exceptions are, unfortunately, CheckedExceptions, so you will have to wrap them, like this:
throw new MyRuntimeException(e);
4. NEVER catch + log + rethrow an Exception
This is a real evilness and should be avoided in 99% of the cases.
When you log an exception and rethrow it, chances are high that it will be logged twice, which will clutter your log and so make it much harder to monitor your system.
Last but not least, think of writing an ExceptionHandling mechanism like an
ExceptionHandlingService, that will encapsulate your exception handling mechanism and uniformly handle all exceptions all over your system.