I am new in java world. In service layer i have this updateCustomer method.
Am i handling exceptions correctly?
Is there a better way to do it?
Any pros and cons with my way of doing it?
#Override
public ResponseEntity<String> updateCustomer(String id, Customer customer) throws ResourceNotFoundException, BadRequestBodyException {
log.info("Updating customer.");
if(!Strings.isNullOrEmpty(customer.getCustomerName())
&& !Strings.isNullOrEmpty(customer.getCustomerType())){
Customer existingCustomer = customerRepository.findCustomerById(id)
.orElseThrow(() -> new ResourceNotFoundException("Error: Customer not found for id - " + id));
existingCustomer.setCustomerName(customer.getCustomerName());
existingCustomer.setCustomerType(customer.getCustomerType());
customerRepository.save(existingCustomer);
return ResponseEntity.ok().body("Customer updated successfully.");
} else {
throw new BadRequestBodyException("Error: request body can not be null or empty!");
}
}
Couple of things here.
I would recommend the service layer not to return the ResponseEntity<T> object instead it should return the DTO or just string in your case.
Also for request body validation use #Valid annotation on the request body model in controller or if you want to handle the validation do that in controller not in service.
The controller class should be responsible for to convert/create the ResponseEntity<?> which sends to server.
In your controller class you can define the method with #ExceptionHandler annotation and specify how you would handle that request. Alternative to that if the exception you are handling is thrown from diffrent services then you can define the class with #ControllerAdvice and specify the exception handling methods there.
With regards to your code you should do the following:
//Controller
#Override
public ResponseEntity<String> updateCustomer(String id, Customer customer)
throws ResourceNotFoundException, BadRequestBodyException {
if (Strings.isNullOrEmpty(customer.getCustomerName())
|| Strings.isNullOrEmpty(customer.getCustomerType())) {
throw new BadRequestBodyException("Error: request body can not be null or empty!");
}
log.info("Updating customer.");
return ResponseEntity.ok(customerService.updateCustomer(id, customer);
}
// The below exception handling method can be part of your controller or you can centralise them
// in class annotated with ControllerAdvice
#ExceptionHandler(BadRequestBodyException.class)
public ResponseEntity<Object> handleBadRequestBodyException(BadRequestBodyException ex) {
var response = new HashMap<String, Object>();
response.put("status", "failure");
response.put("message", "Request body Validation failed.");
response.put("detailMessage", ex.getLocalizedMessage());
return ResponseEntity.internalServerError().body(response);
}
#ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<Object> handle(ResourceNotFoundException ex) {
var response = new HashMap<String, Object>();
response.put("status", "failure");
response.put("message", "Resource not found.");
response.put("detailMessage", ex.getLocalizedMessage());
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(response);
}
//Service
#Override
public String updateCustomer(String id, Customer customer) throws ResourceNotFoundException, BadRequestBodyException {
log.info("Updating customer.");
Customer existingCustomer = customerRepository
.findCustomerById(id)
.orElseThrow(() -> new ResourceNotFoundException("Error: Customer not found for id - " + id));
existingCustomer.setCustomerName(customer.getCustomerName());
existingCustomer.setCustomerType(customer.getCustomerType());
customerRepository.save(existingCustomer);
return "Customer updated successfully.";
}
Hope this helps!
Related
I work with 3-tier architecture in a Spring Boot app. I created 3 packages (model, service, controller), but what I did, service calls a repo function with try catch, and then I call it in controller
Example:
Service:
public ResponseEntity<List<Customer>> getAllCustomers() {
try {
List<Customer> customers = new ArrayList<Customer>();
cutomerRepository.findAll().forEach(customers::add);
if (customers.isEmpty()) {
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
return new ResponseEntity<>(customers, HttpStatus.OK);
} catch (Exception e) {
return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Controller
#GetMapping("/viewList")
private ResponseEntity<?> getAllCustomers()
{
try{
return customerService.getAllCustomers();
}catch (Exception exception){
return new ResponseEntity<String>("Customers is not found", HttpStatus.METHOD_FAILURE);
}
}
Is that correct? I think I should put in services only customerRepository.findAll() without any other logic or code, but I'm not sure. Any idea?
The Service layer should contain logic, so that is OK.
But it should not contain any classes from the Controller layer, as this would leak information from an "upper" layer into a "lower" layer. This means your Service should not return a ResponseEntity as this is from the Controller layer. Instead it should return simply a list of Customers and let the Controller construct the ResponseEntity out of it.
Otherwise your Service will always be limited to be called by this specific Controller. It would not be reusable to be called by another service of a different type of Controller, that does not use an HTTP ResponseEntity.
The best approach in my opinion is the following.
Your Service layer should not return ResponseEntity<List<Customer>> as it currently does. It should instead return List<Customer>.
This is already in the above answer but wanted to answer to extend the content a bit more.
The service also when modified to return List<Customer> should handle the exceptions with Application specific exceptions. So you create your own exception for your application, the model for this exception and also you create an Exception Advice class where all those application exceptions are handled in a general way. So your service will just throw the exception, the controller will not catch it and it will be handled by the Advice class (annotated with #ControllerAdvice) which will handle all the uncaught exceptions and return appropriate responses. There are also some more options to handle exceptions in generic way in Spring.
I am attaching the following code as an example
Class to handle all exceptions that bubble up from controllers.
#ControllerAdvice
public class ErrorHandler {
#ExceptionHandler(ApplicationException.class)
public ResponseEntity handleApplicationException(ApplicationException e) {
return ResponseEntity.status(e.getCustomError().getCode()).body(e.getCustomError());
}
}
Some application specific exception (The name could be more specific)
#Getter
#Setter
public class ApplicationException extends RuntimeException {
private CustomError customError;
public ApplicationException(CustomError customError){
super();
this.customError = customError;
}
}
An Error object to be returned to the client when exception happens
#Getter
#Setter
#NoArgsConstructor
public class CustomError {
private int code;
private String message;
private String cause;
public CustomError(int code, String message, String cause) {
this.code = code;
this.message = message;
this.cause = cause;
}
#Override
public String toString() {
return "CustomError{" +
"code=" + code +
", message='" + message + '\'' +
", cause='" + cause + '\'' +
'}';
}
}
Your Service
public List<Customer> getAllCustomers() {
try {
List<Customer> customers = new ArrayList<Customer>();
cutomerRepository.findAll().forEach(customers::add);
if (customers.isEmpty()) {
throw new ApplicationException(new CustomError(204, "No Content", "Customers do not exist"));
}
return new ResponseEntity<>(customers, HttpStatus.OK);
} catch (Exception e) {
throw new ApplicationException(new CustomError(500, "Server Error", "Disclose to the client or not what the cause of the error in the server was"));
}
}
The controller it self could also inspect the input information that it receives and if needed could throw it self an application specific exception or just return an appropriate response with what is false in the input.
This way the Controller is just handling the input/output between the user and the service layer.
The Service is just handling input/output of data from persistent layer.
help me anybody Please in this issue.
The project, I am working on is old mvc, and is not going to be change to rest, So have to deal with "what we have :) ".
this is my controller method, the class of which is anotated #Controller
#RequestMapping(method=RequestMethod.POST)
public String createSomething(#RequestBody somejson, Model m) throws Exception {
SomeCustomListenerClass listener = new SomeCustomListenerClass(m);
AnnotherClass ac = somejson.toNotification(someService, anotherService, listener);
try {
ac = someService.createSomething(ac, listener);
m.addAttribute("success", true);
m.addAttribute("notificationId", ac.getId());
}
catch(SawtoothException ex) {
return handleError(ex, "Create Notification", listener);
}
return "structured";
}
and this one is handleError method body
private String handleError(Exception ex, String operation, SomeCustomListenerClass listener) {
if (!listener.hasErrors()) {
log.error("Unexpected error getting notification detail", ex);
listener.error("notification.controllerException", operation);
}
return "error";
}
Now I am getting the right errors in the client side, say in browser, but also getting the status code 500
now my boss says that we have to get 400, when validation errors hapens, not 500, as is now.
So, Please help me guys, how to overcome to this problem.
You can extend your exceptions and throw them on your controller:
#ResponseStatus(value=HttpStatus.BAD_REQUEST, reason="Your exception message")
public class YourCustomException extends RuntimeException {
}
Or you can use an ExceptionControllerHandler:
#Controller
public class ExceptionHandlingController {
// #RequestHandler methods
...
// Exception handling methods
// Convert a predefined exception to an HTTP Status code
#ResponseStatus(value=HttpStatus.CONFLICT,
reason="Data integrity violation") // 409
#ExceptionHandler(DataIntegrityViolationException.class)
public void conflict() {
// Nothing to do
}
// Specify name of a specific view that will be used to display the error:
#ExceptionHandler({SQLException.class,DataAccessException.class})
public String databaseError() {
// Nothing to do. Returns the logical view name of an error page, passed
// to the view-resolver(s) in usual way.
// Note that the exception is NOT available to this view (it is not added
// to the model) but see "Extending ExceptionHandlerExceptionResolver"
// below.
return "databaseError";
}
// Total control - setup a model and return the view name yourself. Or
// consider subclassing ExceptionHandlerExceptionResolver (see below).
#ExceptionHandler(Exception.class)
public ModelAndView handleError(HttpServletRequest req, Exception ex) {
logger.error("Request: " + req.getRequestURL() + " raised " + ex);
ModelAndView mav = new ModelAndView();
mav.addObject("exception", ex);
mav.addObject("url", req.getRequestURL());
mav.setViewName("error");
return mav;
}
}
Try the #ExceptionHandler annotation or #ControllerAdvice to create custom exception handling mechanisms:
https://www.tutorialspoint.com/spring_boot/spring_boot_exception_handling.htm
add #ResponseStatus(HttpStatus.BAD_REQUEST) on top of handleError(...) method.
#ExceptionHandler({ Throwable.class })
#ResponseStatus(HttpStatus.BAD_REQUEST)
public String handleError(...) {
...
}
I am having different projects for Service and Web. I would like to know how to handle when specific exception comes from Services. For example I am handling DuplicateDataException as follows at Service side:
public void serviceFunction()
{
try
{
//code
}catch(DuplicateDataException e)
{
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(e.getMessage()).build();
}}
At UI side: controller class is calling the service function through Rest API
#RequestMapping(value = "/addNew", method = RequestMethod.POST)
public ModelAndView addNew(Object obj) {
try {
restTemplate.exchange(url, HttpMethod.POST, httpEntity,
Object.class);
LOGGER.info("Object Created Successfully");
} catch (Exception e) {
return ModelAndView("PageName", "param","value");
}
}
At UI side I am getting Internal Server Error, Instead I would like to get the entity error message value which was set at service side.
As a kind of best practice try to catch your exceptions in your service code and throw an RuntimeException("An error occured") or a self defined Exception which extends Java's RuntimeException. Then you can define a global ExceptionHandler for all of your controllers and return your own error page like:
#ControllerAdvice
public class MyExceptionHandler {
#ResponseStatus(HttpStatus.NOT_FOUND)
#ExceptionHandler(Exeption.class)
public ModelAndView handleFileNotFoundException(Exception exception){
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("yourView");
modelAndView.addObject("exception", exception);
return modelAndView;
}
}
I'm using Spring #ControllerAdvice to handle exceptions
#ControllerAdvice
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(value = { DataIntegrityViolationException.class})
#ResponseBody
public ResponseEntity<String> unknownException(Exception ex, WebRequest req) {
return new ResponseEntity<>(ex.getCause().getMessage(), new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR);
}
The problem i'm experiencing is that when the exception occurs (when i send a request via swagger), i do not get an expected exception message, but :
{"error": "no response from server"}
Response Code : 0
Response Body : No Content
I can clearly see in debug mode that the method annotated by #ExceptionHandler is called.
I've experimented with method return types, #ResponseBody, #ResponseStatus annotations and a few other thing that came to mind, but it seems that i only get some non-empty response when i return a ResponseEntity without a body, e.g.
ResponseEntity.noContent().build()
or
ResponseEntity.ok().build()
In such cases i get correct http code and a few headers
Please advise on what i'm doing wrong
Spring version 4.3.9
Spring boot version 1.5.4
Thank you in advance
UPD
I carried on experimenting and this is the solution that worked for me.
It is quite close to one of the answers - i will mark that one as accepted
In short, i just created my own dto class , populated the instance with the exception details i was interested in and returned it directly
My code
#ExceptionHandler(value = { DataIntegrityViolationException.class})
#ResponseStatus(code = HttpStatus.INTERNAL_SERVER_ERROR)
#ResponseBody
public ExceptionDetailHolder unknownException(Exception ex, WebRequest req) {
final Throwable cause = ex.getCause();
return new ExceptionDetailHolder("Error interacting with the database server",
cause.getClass() + ":" + cause.getMessage(),
cause.getCause().getClass() + ":" + cause.getCause().getMessage()
);
}
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Setter
private class ExceptionDetailHolder {
private String message;
private String exceptionMessage;
private String innerExceptionMessage;
}
Results (which also show the contents of ex.getMessage and ex.getCause().getMessage() as asked by commenters) :
{
"message": "Error interacting with the database server",
"exceptionMessage": "class org.hibernate.exception.ConstraintViolationException:could not execute statement",
"innerExceptionMessage": "class com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException:Column 'allow_copay_payments' cannot be null"
}
My way of handling exception is like below, I find the specific exception and then create my own class object ValidationErrorDTO in this case, then populate required fields in that class (ValidationErrorDTO):
#ExceptionHandler(HttpMessageNotReadableException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
public ResponseEntity<ValidationErrorDTO> processValidationIllegalError(HttpMessageNotReadableException ex,
HandlerMethod handlerMethod, WebRequest webRequest) {
Throwable throwable = ex.getMostSpecificCause();
ValidationErrorDTO errorDTO = new ValidationErrorDTO();
if (throwable instanceof EnumValidationException) {
EnumValidationException exception = (EnumValidationException) ex.getMostSpecificCause();
errorDTO.setEnumName(exception.getEnumName());
errorDTO.setEnumValue(exception.getEnumValue());
errorDTO.setErrorMessage(exception.getEnumValue() + " is an invalid " + exception.getEnumName());
}
return new ResponseEntity<ValidationErrorDTO>(errorDTO, HttpStatus.BAD_REQUEST);
}
In my request handler I want to do some validation and based on the outcome of validation check I will return different response (success/error). So I create a abstract class for the response object and make 2 subclasses for failure case and successful case. The code looks something like this, but it doesn't compile, complaining that errorResponse and successResponse cannot be converted to AbstractResponse.
I'm quite new to Java Generic and Spring MVC so I don't know of a simple way to solve this.
#ResponseBody ResponseEntity<AbstractResponse> createUser(#RequestBody String requestBody) {
if(!valid(requestBody) {
ErrorResponse errResponse = new ErrorResponse();
//populate with error information
return new ResponseEntity<> (errResponse, HTTPStatus.BAD_REQUEST);
}
createUser();
CreateUserSuccessResponse successResponse = new CreateUserSuccessResponse();
// populate with more info
return new ResponseEntity<> (successResponse, HTTPSatus.OK);
}
There are two problems here:-
Your return type has to be changed to match the two response subclasses ResponseEntity<? extends AbstractResponse>
When you instantiate your ResponseEntity you cannot use the simplified <> syntax you have to specify which response class you are going to use new ResponseEntity<ErrorResponse> (errResponse, HTTPStatus.BAD_REQUEST);
#ResponseBody ResponseEntity<? extends AbstractResponse> createUser(#RequestBody String requestBody) {
if(!valid(requestBody) {
ErrorResponse errResponse = new ErrorResponse();
//populate with error information
return new ResponseEntity<ErrorResponse> (errResponse, HTTPStatus.BAD_REQUEST);
}
createUser();
CreateUserSuccessResponse successResponse = new CreateUserSuccessResponse();
// populate with more info
return new ResponseEntity<CreateUserSuccessResponse> (successResponse, HTTPStatus.OK);
}
Another approach would be using error handlers
#ResponseBody ResponseEntity<CreateUserSuccessResponse> createUser(#RequestBody String requestBody) throws UserCreationException {
if(!valid(requestBody) {
throw new UserCreationException(/* ... */)
}
createUser();
CreateUserSuccessResponse successResponse = new CreateUserSuccessResponse();
// populate with more info
return new ResponseEntity<CreateUserSuccessResponse> (successResponse, HTTPSatus.OK);
}
public static class UserCreationException extends Exception {
// define error information here
}
#ExceptionHandler(UserCreationException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
public ErrorResponse handle(UserCreationException e) {
ErrorResponse errResponse = new ErrorResponse();
//populate with error information from the exception
return errResponse;
}
This approach enables the possibility of returning any kind of object, so an abstract super class for the success case and the error case (or even cases) is no longer necessary.