What are best practices for designing APIs regarding exceptions?
Since methods should either change the internal state of an object or return a value, signaling an exception state via return code is bad practice. Use exceptions since they keep your API clean.
Every exception, checked or unchecked, should be precisely documented. The phrase starts with the word "if" as in
* ... * @throws IllegalArgumentException if the given input value is empty or contains whitespaces. * ...
If an exception is thrown by a sub module you use but have not in control, you should hesitate to put this exception into your API. This would make you dependent on the sub module and if the exception type thrown in this sub module changes, your API is also affected. Often the exception of the sub module is in another level of abstraction. Therefore you should catch that sub module's exception and create and throw one of your own. This will keep your API clean and rest you in control of it.
There are two forms of dealing with this
Limit the number of exceptions you throw from a method. If the method handles many exceptions try to use Exception Translation or Exception Chaining and map them to one exception to be declared as thrown.
Although the the number of exceptions should be kept small, every exception should be declared. This adresses Subclasses of exceptions like java.io.FileNotFoundException that is a subclass of java.io.IOException.
public void execute() throws FileNotFoundException, IOException { ... }
This allows the client code to catch and handle exceptions individually. Without the declaration (and documentation) the developer has no chance but reading the source code to tell which exception are thrown by the method.
Although the the number of exceptions should be kept small, runtime exceptions should be declared and documented.
public void execute() throws IllegalArgumentException, MyAppRuntimeException { ... }
Without the documentation, the developer has no clue, which exceptions are thrown by the method, since only checked exceptions are required to be declared. The recommendation is to declare and document all runtime exceptions thrown by a method.
This does no extend to declaring eventual programming problems like NullPointerExceptions, but includes NullPointerExceptions if preconditions of a method are checked.
A method should only throw exceptions on the same level of abstraction as the method itself. Methods on the presentation tier should normally not deal with java.io.IOException or java.sql.SQLException that bubble up from the resource tier. Instead the business tier should provide useful exceptions on the abstraction level of the business domain.
Especially do not mix exceptions of different abstraction levels in the throws clause of a method.