Throwing and using the correct exceptions also has its downfall. If your method can potentially throw multiple low-level exceptions, it can sometimes give too much information to the calling code. Take the following example:
public int doSomethingComplicated() throws IllegalArgumentException, SQLException, IOException;
This example causes all the calling methods to catch three different types of exceptions that all result in the same thing: Your method failed. Instead, translate them into a single exception:
public int doSomethingComplicated() throws ComplicatedException;
In this "wrapper" exception, contain a reference to the original lower-level exception as well as information about what caused the exception. This way, the higher-level method can look deeper into the issue if it wants to or else it can just accept that the lower-level method failed and then move on.