Best practice for handling many exceptions
https://softwareengineering.stackexchange.com/questions/371778
-
06-02-2021 - |
Question
I have Java microservice that exposes multiple APIs. Under each API there are many exceptions could be thrown. The last method that catches them all has big catch
block ...
catch(exc1 e1 | exc2 e2 | exc3 e3 ..... | exc10 e10){ ...}
They are all of the same level. Now I thought of combining them all under one exception. So, exc1 to exc10 extend Api1Exception
. So I could jus catch it like this:
catch(Api1Exceptione){ ... }
Cleaner, and easier to control. However. I realized other APIs share some of Api1Exception
exceptions. Let's say API2, is throwing something like:
catch(exc13 e13 | exc2 e2 | exc16 e16 ..... | exc19 e19){ ...}
I do not want to catch the entire Api1Exception
, because some of them are not thrown in that API2 logic. So, I had to create another exception Api2Exception
and I had create another copy of say exc2
to be a child of Api2Exception
. This to me looks like redundant code, and I thought there should be better way to handle this. Any idea?
UPDATE
Following the answer, this is what I did:
- created
CustomException
that extendsException
, with fielderrorCode
- Every custom exception I create, extends
CustomException
and pass theerrorCode
along with themessage
I catch it like this:
catch(CustomException ce){ handleCustomException(ce) }
handling method:
if(ce.getErrorCode().eqauls(SOME_ERROR_CODE){ //do something } else if (ce.getErrorCode().eqauls(SOME_ERROR_CODE){ //do something else } //....... else{ throw new Exception .... }
P.S.: Not sure why my question got downvoted. Whoever did it, it would be great if you provide some feedback.
Solution
The standard approach is to create one class ApiException
that is thrown by all public APIs of the library, and wrap the underlying exception into it:
try {
}
catch (Exception e) {
throw new ApiException(e);
}
If a user's catch
block needs special handling for some of the underlying exceptions, it can unwrap it (with .getCause()
) and if it's not what's needed, rethrow the original exception.
The idea is that if there are all sorts of possible underlying causes unrelated to code's logic, the code can't do anything intelligent about it, and this rather requires the intervention of the app's admin. So it just passes it upstream, possibly wrapping with its own type, with the idea that it will eventually be logged.
If the code is supposed to actually be able to resolve the problem, the function should return concrete exceptions that are in line with the nature of its work, and no more than a few of them. Failing to keep the number small in this case means you probably need to split the function into a few more specialized ones which the user will call in sequence.