Why shouldn't Java throw multiple exceptions?

876    Asked by LeonardTerry in Java , Asked on Oct 11, 2022

We use SonarQube to analyze our Java code and it has this rule (set to critical):


Public methods should throw at most one checked exception

Using checked exceptions forces method callers to deal with errors, either by propagating them or by handling them. This makes those exceptions fully part of the API of the method.


To keep the complexity for callers reasonable, methods should not throw more than one kind of checked exception."


Another bit in Sonar has this:


Public methods should throw at most one checked exception

Using checked exceptions forces method callers to deal with errors, either by propagating them or by handling them. This makes those exceptions fully part of the API of the method.


To keep the complexity for callers reasonable, methods should not throw more than one kind of checked exception.


The following code:


public void delete() throws IOException, SQLException {      // Non-Compliant
  /* ... */
}
should be refactored into:
public void delete() throws SomeApplicationLevelException {  // Compliant
    /* ... */
}

Overriding methods are not checked by this rule and are allowed to throw several checked exceptions.


I've never come across this rule/recommendation in my readings on exception handling and have tried to find some standards, discussions etc. on the topic. The only thing I've found is this from CodeRach: How many exemptions should a method throw at most?


Is this a well accepted standard?

Answered by Siddharth verma

The reason that you would, ideally, want to only throw one type of exception instead of opting for Java to throw multiple exceptions is because doing otherwise likely violates the Single Responsibility and Dependency Inversion principles. Let's use an example to demonstrate.


Let's say we have a method that fetches data from persistence, and that persistence is a set of files. Since we are dealing with files, we can have a FileNotFoundException:

public String getData(int id) throws FileNotFoundException

Now, we have a change in requirements, and our data comes from a database. Instead of a FileNotFoundException (since we are not dealing with files), we now throw an SQLException:

public String getData(int id) throws SQLException

We would now have to go through all code that uses our method and change the exception we have to check for, else the code won't compile. If our method gets called far and wide, that can be a lot to change/have others change. It takes a lot of time, and people aren't going to be happy.

Dependency inversion says that we really shouldn't throw either of these exceptions because they expose internal implementation details we are working to encapsulate. Calling code needs to know what type of persistence we are using, when it really should just be worried about if the record can be retrieved. Instead we should throw an exception that conveys the error at the same level of abstraction as we are exposing through our API:

public String getData(int id) throws InvalidRecordException

Now, if we change the internal implementation, we can simply wrap that exception in an InvalidRecordException and pass it along (or not wrap it, and just throw a new InvalidRecordException). External code does not know or care what type of persistence is being used. It's all encapsulated.

As for Single Responsibility, we need to think about code that throws multiple, unrelated exceptions. Let's say we have the following method:

public Record parseFile(String filename) throws IOException, ParseException

What can we say about this method? We can tell just from the signature that it opens a file and parses it. When we see a conjunction, like "and" or "or" in the description of a method, we know that it is doing more than one thing; it has more than one responsibility. Methods with more than one responsibility are hard to manage as they can change if any of the responsibilities change. Instead, we should break methods up so they have a single responsibility:

public String readFile(String filename) throws IOException

public Record parse(String data) throws ParseException

We've extracted out the responsibility of reading the file from the responsibility of parsing the data. One side effect of this is that we can now pass in any String data to the parse data from any source: in-memory, file, network, etc. We can also test parse more easily now because we don't need a file on disk to run tests against.

Sometimes there really are two (or more) exceptions we can throw from a method, but if we stick to SRP and DIP, the times we encounter this situation become rarer.



Your Answer

Interviews

Parent Categories