There are a few rules for exception handling that you should bear in mind. But first, you need to remember that exceptions are part of the interface exposed by the code; document them. This is especially important when the interface is a public one, of course, but it's a very good idea in private interfaces as well.
Exceptions should only be handled at the point where the code can do something sensible with them. The worst handling option is to do nothing at all about them, which should only be done when that is exactly the correct option. (When I have such a situation in my code, I include a comment to that effect so I know not to worry about the empty body.)
The second worst option is to throw an unrelated exception without the original attached as a cause. The issue here is that the information within the original exception that would allow diagnosis of the problem is lost; you're creating something that nobody can do anything with (other than complain that “it doesn't work”, and we all know how we hate those bug reports).
Much better is logging the exception. That lets someone find out what the problem is and fix it, but you should only log the exception at the point where it would otherwise be lost or reported over an external connection. That's not because logging more often is a major problem as such, but rather because excessive logging means you just get the log consuming more space without containing more information. Once you've logged the exception, you can report a précis to the user/client with a good conscience (as long as you also include the time of generation — or other correlation identifier — in that report so that the short version can be matched up with the detail if necessary).
The best option is, of course, to completely handle the exception, dealing with the error situation in its entirety. If you can do this, by all means do it! It might even mean that you can avoid having to log the exception.
One way to handle an exception is to throw another exception that provides a higher-level description of the problem (e.g., “failed to initialize
” instead of “index out of bounds
”). This is a good pattern so long as you don't lose the information about the cause of the exception; use the detailed exception to initialize the cause
of the higher-level exception or log the detail (as discussed above). Logging is most appropriate when you're about to cross an inter-process boundary, such as an IPC call, because there's no guarantee that the low-level exception class will be present at all on the other end of the connection. Keeping as an attached cause is most appropriate when crossing an internal boundary.
Another pattern you see is catch-and-release:
try {
// ...
} catch (FooException e) {
throw e;
}
This is an anti-pattern unless you've got type constraints from other catch
clauses that mean you can't just let the exception go past on its own. Then it's just an ugly feature of Java.
There's no real difference between checked exceptions and unchecked ones other than the fact that you must declare checked exceptions that cross method boundaries. It's still a good idea to document unchecked exceptions (with the @throws
javadoc comment) if you know they're being thrown deliberately by your code. Don't deliberately throw java.lang.Error
or its subclasses (unless you're writing a JVM implementation).
Opinion: An unexpected error case always represents a bug in your code. Checked exceptions are a way of managing this threat, and where developers are deliberately using unchecked exceptions as a way to avoid the trouble of handling error cases, you're building up a lot of technical debt that you'll have to clean up some time if you want robust code. Sloppy error handling is unprofessional (and looking at the error handling is a good way to determine how good a programmer really is).
@throws
tag, which by the way is a good practice.@throws
tag to document each unchecked exception that a method can throw, but do not use the throws keyword to include unchecked exceptions in the method declaration.