Reputation: 412
I have the following Java method:
public Class createClass(Class class) {
try {
// retrieve the professor of the class and check if he exists
Professor professorFound = professorRepository.findById(class.getProfessorId());
if (professorFound != null) {
// if the professor exists, then check if he already has a class
// with the same id
List<Class> classes = professorFound.getClasses();
List<Class> classFound = classes.stream().... // loop to find the class...
// if he has, throw an exception
if(!classFound.isEmpty()) {
throw new ClassAlreadyRegisteredException();
} else {
// if he does not have, then create the class
Class class = new Class();
professorFound.getClasses().add(class);
return professorRepository.save(professorFound);
}
} else {
// if the professor does not exist, throw an exception
throw new ProfessorNotFoundException();
} catch (Exception e) {
// if there is any other error during the communication with the database,
// throw a generic IOException
throw new ClassIOException();
}
}
Basically, what I need is to throw specific Exceptions (if the professor informed in the request does not exist, or if the professor already has a class with the same id), or throw a generic IOException
if there is any other error during the communication with the database.
However, in the way that I have developed, if any specific Exception
is thrown, the try block will catch the exception and will throw a generic IOException.
How can I solve this problem?
I'm very interested in understanding what are the best practices in this case. Should I catch each specific exception separately and throw them twice? Is that a good practice?
EDIT:
This is how my ClassAlreadyRegisteredException
looks like:
public class ClassAlreadyRegisteredException extends ApiException {
private static final long serialVersionUID = 1L;
public ClassAlreadyRegisteredException(String code, String message, String developerMessage, String origin, HttpStatus status) {
super(code,message,developerMessage,origin, status);
}
}
This is how my ApiException
looks like:
@Data
@AllArgsConstructor
@RequiredArgsConstructor
@EqualsAndHashCode(callSuper = false)
public class ApiException extends RuntimeException{
private static final long serialVersionUID = 1L;
private String code;
private String userMessage;
private String developerMessage;
private String origin;
private HttpStatus status;
}
Thanks in advance.
Upvotes: 1
Views: 10763
Reputation: 1441
Catch and re-throw.
try {
... same as before ...
} catch (ClassAlreadyRegisteredException | ProfessorNotFoundException e) {
throw e;
} catch (Exception e) {
// if there is any other error during the communication with the database,
// throw a generic IOException
throw new ClassIOException();
}
Alternatively, remember the exception to throw it later.
Exception fail = null;
try {
….
// save exception in place of existing throws
// for example:
fail = new ClassAlreadyRegisteredException();
…
} catch (Exception ex) {
...same as original...
}
if (fail != null) {
throw fail;
}
I use both techniques; the choice depends on what is simpler in any given situation. Neither is uncontestably better.
For the catch and re-throw method, you have to keep the list of caught-and-rethrown exceptions consistent with the exceptions you actually throw from within the try-clause. In larger cases, I'd avoid that problem by using an exception hierarchy, so I could catch the common base class.
For the save-and-throw method, you have to arrange control flow so that nothing significant is done after detecting the failure, since you don't have the immediate 'throw' command to exit the try-clause. Nevertheless there are cases where it is simple enough; the original example is one such.
Upvotes: 6
Reputation: 3421
It's totally acceptable to throw an exception in a catch block. A common use case is to take a checked Exception
and throw a unchecked RuntimeException
which would allow the exception bubble up to where it needs to be handled.
You'll want to use checked exceptions for use cases such as Connectivity/IO, SQL exceptions..
To answer your question, in most libraries that connect to the database, an checked IOException
is thrown if there are any connectivity issues. For these cases, you can always specify this in the method signature public Class createClass() throws IOException
this specifies that any callers of createClass()
has to fulfill the contract that the IOException
is handled.
or
You can rethrow this as a RuntimeException
try {
...
} catch (IOException e) {
throw new RuntimeException(e); // <- send the original exception so you can preserve the exception and stacktrace.
}
This will essentially bubble up to STDOUT or whatever handler your framework specifies.
However, catching an cover all Exception
and throwing a more specific ClassIOException
can have unintended consequences.
If you have a NullPointerException
this will be captured by your catch (Exception e)
block and rethrown as a ClassIOException
Doing this will corrupt the stacktrace and cause your error logs to be much more difficult to debug.
Another tip is to consider what your Exception
cases are.
If they are standard flow of the application, service, or business logic -- these may not be appropriate exceptions.
The ClassAlreadyRegisteredException
and ProfessorNotFoundException
may not be exceptional cases in your application... unless these are already specified by your professor.
Sometimes these can be thrown as RuntimeException
if the situation calls for it.
There are still many opinions on how exceptions are handled. So here are some rule of thumb questions I ask myself when working with exceptions
Upvotes: 2