I am developing an application in Spring Boot. I was wondering what is the best way to handle exceptions. So following is my code,
ExceptionHandler.java
I am using #ControllerAdvice. What is the best to use? #ControllerAdvice or #RestControllerAdvice?
#ControllerAdvice
public class ExceptionHandler {
#ExceptionHandler(NotFoundException.class)
public ResponseEntity<ErrorResponse> notFound(NotFoundException notFoundException) {
ErrorResponse errorResponse = new ErrorResponse(HttpStatus.NOT_FOUND, notFoundException.getMessage());
return new ResponseEntity<ErrorResponse>(errorResponse, HttpStatus.NOT_FOUND);
}
}
And implemented service layer #Service as follows,
#Override
public User update(User newUser) throws NotFoundException {
Optional<User> user = userRepository.findById(newUser.getId());
if (!user.isPresent()) throw new NotFoundException("Record Not Found!");
return userRepository.save(newUser);
}
In Controller.java code is as follows,
#PutMapping
public User update(#RequestBody User user) {
User updatedUser = userService.update(user);
if (updatedUser == null) throw new SomeOtherException("Exception while Updating!");
return updatedUser;
}
So my questions are:
Is the above approach bad? Is it okay to throw exceptions in the service layer and will it catch automatically by #controlleradvice? Or need to throw only in the controller? I am seeking the best practice to handle exceptions.
The way you used is perfectly fine.
As #ControllerAdvice/#RestControllerAdvice is used to handle exceptions globally throughout the controllers [flow].
You can throw exceptions in #Service layel also. [That is the same thing]
ControllerAdvice will catch it.
You can customize your Exceptions your own way.
I also like your approach. Maybe you want to have a look at Lombok and #SneakyThrows, too: I find it unneccessary to have throws XYZ on the methods, when at the end you handle them globally anyway.
Related
By default, Spring Boot does not return messages for any exceptions, including ResponseStatusException, meaning that the message about bar below will not be returned to the client:
#GetMapping("/foo")
#ResponseBody
public Foo getFoo(#RequestParam(name = "bar", defaultValue = "0") int bar) {
if (bar <= 0) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "bar must always be positive");
}
return example.getFoo(bar);
}
This can be changed by setting server.error.include-message=always in the application.properties, however this causes ALL exception messages to be returned to the client, including this one:
#GetMapping("/baz")
#ResponseBody
public Baz getBaz() {
if (!security.checkSecurity()) {
throw new RuntimeException("Security breach! Hope no one finds out!");
}
return example.getBaz();
}
I know this is a trivial example and the solution would be just "don't throw server exceptions from your controller", but the exception might actually come from some other code buried deep in the application, it could even be a NullPointerException or whatever.
How can I get the application to show messages only from ResponseStatusException and not other types of exception? (I guess other than adding try-catch clauses to every single controller method.)
You can add extra (#ExceptionHandler) methods to any controller to specifically handle exceptions thrown by request handling (#RequestMapping) methods in the same controller. Such methods can:
Handle exceptions without the #ResponseStatus annotation (typically predefined exceptions that you didn’t write)
Redirect the user to a dedicated error view
Build a totally custom error response
Controller advice allows you to use exactly the same exception handling techniques but apply them across the whole application, not just to an individual controller. You can think of them as an annotation-driven interceptor.
Any class annotated with #ControllerAdvice becomes a controller-advice and three types of method are supported:
Exception handling methods annotated with #ExceptionHandler.
Model enhancement methods (for adding additional data to the model) annotated with #ModelAttribute. Note that these attributes are not available to the exception handling views.
Binder initialization methods (used for configuring form-handling) annotated with
#InitBinder.
Solution:
#ControllerAdvice
public class RestControllerAdvice {
#ExceptionHandler(ResponseStatusException.class)
public ResponseEntity<String> handleStatusException(ResponseStatusException exception) {
throw exception;
}
#ExceptionHandler(Exception.class)
public ResponseEntity<String> handleException(Exception exception) {
return new ResponseEntity<>("Exception", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Catch all the exceptions and in the catch block throw ResponseStatusException like in:
#GetMapping("/actor/{id}")
public String getActorName(#PathVariable("id") int id) {
try {
return actorService.getActor(id);
} catch (ActorNotFoundException ex) {
throw new ResponseStatusException(
HttpStatus.NOT_FOUND, "Actor Not Found", ex);
}
}
I have a simple spring boot application in which I have alot of tables. I have build their models, repositories, service and controller files. I have also tested all the apis through postman.
Now I need to implement custom exception in my models. Since I am at the beginning stage and learning things, I am a little confused as to how can I apply exceptions?
From what I have explored, I need to create three files
ErrorDetails.java
GlobalExceptionHandler.java
ResourceNotFoundException.java
Is this correct? If yes suppose I have added these files in my project . How do I implement these exceptions in my apis? Can anybody help me? Would mean alot. Thanks!
Whenever there is a case where resources will be not available then throw ResourceNotFoundException i.e. throw new ResourceNotFoundException("Error message of your choice");
For example in class CustomerTypeRepository within method getCustomerTypebyID instead of below code:
if (a == null) {
return ResponseEntity.notFound().build();
}
you can write
if (a == null) {
throw new ResourceNotFoundException("Customer type doesn't exist with the given id: "+Id);
}
and after that #ControllerAdvice GlobalExceptionHandler has already implamented for ResourceNotFoundException handler. So no need to worry about.
I believe in declaring checked exception as a contract, so I would do something like this
#Service
public class CustomerCategorizationService {
#Autowired
private CustomerTypeRepository customerTypeRepository;
// -------------- CustomerType API ----------------------- ///
public CustomerType saveCustomerType(CustomerType obj) throws ServiceException {
Where ServiceException is custom checked exception defined in application.
public CustomerType saveCustomerType(CustomerType obj) throws ServiceException {
//Other code
Long id;
try {
id = customerTypeRepository.save(obj);
}catch(DataAccessException cause) {
throw new ServiceException(cause.getMessage());
}
return id;
}
And in #ControllerAdvice
#ExceptionHandler(ServiceException.class)
public ResponseEntity<?> resourceNotFoundException(ServiceException ex, WebRequest request) {
ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(),
request.getDescription(false));
return new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND);
}
We can go one step further and throw custom exception from Controller class (say ResourceException) which will wrap ServiceException. In that case my #ControllerAdvice needs to only deal with ResourceException
At the moment, I'm throwing RuntimeException's to return GraphQL validation errors. It works surprisingly well, with the exception that it throws horrible errors with large stack traces in my logs.
Here you can see I'm checking the submitted new user registration mutation to be sure the passwords match one another and the email address isn't already in use.
What is the correct way to do this in GraphQL SPQR Spring Boot Starter.
#GraphQLMutation (name="register")
public User register(#GraphQLArgument(name="firstname") String firstname, #GraphQLArgument(name="lastname") String lastname, #GraphQLArgument(name="email") String email, #GraphQLArgument(name="msisdn") String msisdn, #GraphQLArgument(name="password") String password, #GraphQLArgument (name="confirmPassword") String confirmPassword) {
if (userRepo.findByEmail(email) != null) {
throw new RuntimeException("User already exists");
}
if (!password.equals(confirmPassword)) {
throw new RuntimeException("Passwords do not match");
}
User newUser = new User();
//...
return userRepo.save(newUser);
}
It's unclear to me what you're asking... but I'll assume you want to customize what's getting logged.
For starters, I'd suggest a dedicated exception type like ValidationException, that you can catch and handle differently.
As for the logging, it's probably happening in grapqh-java as SPQR doesn't log anything by itself. By default graphql-java uses SimpleDataFetcherExceptionHandler which logs the exceptions caught during field resolution.
You now have a couple of options, you could register a ResolverInterceptor in SPQR that catches validation exceptions and logs what you want, and returns a DataFetcherResult with the error message for the user. Because no validation exceptions bubble up to graphql-java, DataFetcherExceptionHandler has nothing to do in this scenario.
It would look something like:
public class ValidationInterceptor implements ResolverInterceptor {
#Override
public Object aroundInvoke(InvocationContext context, Continuation continuation) throws Exception {
try {
return continuation.proceed(context);
} catch (ValidationException e) {
log.warning(e);
return DataFetcherResult.newResult()
.error(GraphqlErrorBuilder
.newError(context.getResolutionEnvironment().dataFetchingEnvironment)
.message(e.getMessage()) //the message for the user
.build());
}
}
}
Look at the answer here for instructions on registering your custom interceptor with Spring Boot.
Another option would be to replace the DataFetcherExceptionHandler graphql-java uses. To do that you must construct GraphQL object yourself and register it as a bean.
#Bean
public GraphQL graphQL(GraphQLSchema schema) {
GraphQL.Builder builder = GraphQL.newGraphQL(schema)
.queryExecutionStrategy(new AsyncExecutionStrategy(customExceptionHandler))
.mutationExecutionStrategy(new AsyncSerialExecutionStrategy(customExceptionHandler));
return builder.build();
}
I also wouldn't be surprised if there's a Spring feature somewhere that can be used for exception handling on managed beans.
I am building a REST API with Spring boot and DAO layer is implemented in Hibernate.I need to understand the correct way of throwing and handling Exception in the Application.Currently I am doing it in this way
#Repository
public class UserDaoImpl
{
public getAllUsers() throws Exception
{
//get All Users from DB
}
}
#Service
public class UserServiceImpl
{
public getAllUsers throws MyCustomException
{ try{
userDaoImpl.getAllUsers();
}
catch(Exception e)
{
throw MyCustomException();
}
}
}
and In Exception Mapper
#ControllerAdvice
public class ApplicationExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler({MyCustomException.class})
#ResponseBody
public ResponseEntity<?> handleCustomException(Exception e) {
log.error("", e);
Map<String, String> error = new HashMap<String, String>();
error.put("message", e.getMessage());
return new ResponseEntity<>(error, HttpStatus.NOT_ACCEPTABLE, MessageResource.getLogMessage("BAD_REQUEST_EXCEPTION"));
}
}
public class MyCustomException extends RuntimeException
{
///// ....
}
So I have added throws clause (throws Exception) in DAO layer and catch at service layer and wrap it in Custom Exception(unchecked exception) and do not propogate the exception at controller layer.
Is this correct ? or there is some better way?
I'd recommend you to have general #ExceptionHandler({Exception.class}) for all cases that you don't want to handle specifically.
Also it's okay to create separate exception classes for situations that require custom handling.
It depends on what do you want to achieve.
About your case. Exception in DAO layer does not necessary mean that request was wrong or did not provide correct parameters. It could be mapping problems, DB access problems and etc. So I would not wrap it to my custom exception, or at lest wrap in to general DataAccessException, make good logging around that and return some general error code to the client.
I have dao, service and action classes in my spring mvc application.
I am throwing Exception in Dao and Service classes. Now in Action, normally I have to write try catch block and in case exception occurs in dao and service, it will be thrown from there and it will go in catch block in action.
I have a error jsp which will be displayed.
Problem is I need to write same catch block in all action methods.
Is it possible to throw it again in action methods too and handle it from a single point rather than writing same code everywhere.
Please suggest.
You can also have a look at Spring Integration. It provides the use of gateways, filters and channels. Each can have a Request, Response and Error channel assigned. Or there is even a default error handler. In case all data flows through a specific channel, having a custom error handler is as simple as follows:
#MessageEndpoint
public class MyErrorHandler {
#ServiceActivator(inputChannel = "errorChannel")
public String handle(String messsage) {
// do whatever you like
}
}
The Integration framework offers lots of usefull stuff for general handling.
I think you are looking for cross-cutting exception handling and good news, you are working with Spring MVC yes you can use this feature.
All you need to do, is throw your CustomExcptions or whatever other Exceptions that are from your services to your action methods.
Let's say here is your service:
#Service
public class MyService {
public void someMethod throws RuntimeException {
...
}
}
In your controller method:
#Controller
public class MyController {
#Autowired
MyService service;
#RequestMapping("/someuri"){
try {
service.someMethod();
} catch {
throw new RuntimeException();
}
}
#ExceptionHandler(RuntimeException.class)
public ModelAndView handleException(RuntimeException ex) {
ModelAndView model = new ModelAndView("errorpage");
return model;
}
}
The handleException method annotated with ExceptionHandler is your advice method for exception handling and it will be called anytime a RuntimeException is throw inside your controller and you can keep up like this for all other exceptions.