Here,my requirement is that i want separate code in my application for exception handling,i saw a nice option of spring there using #controller advice to handle exceptions globally.
#ControllerAdvice
class GlobalControllerExceptionHandler {
#ResponseStatus(HttpStatus.CONFLICT) // 409
#ExceptionHandler(DataIntegrityViolationException.class)
public void handleConflict() {
// Nothing to do
}
}
But there i want to cutomization there,like proper dynamic messages,own error code. so how can i do this,i am new to spring boot and even i don't have knowledge of spring.Need basic example.
You can come up with a class like this to capture information to be sent in response in case of exception:-
public class APIResponse {
int errorCode;
String description;
String someInformation;
// any other information that you want to send back in case of exception.
}
#ControllerAdvice
class GlobalControllerExceptionHandler {
#ResponseStatus(HttpStatus.CONFLICT) // 409
#ResponseBody
#ExceptionHandler(DataIntegrityViolationException.class)
public APIResponse handleConflict(DataIntegrityViolationException exception) {
APIResponse response = createResponseFromException(exception);
return response;
}
}
In your controller advice class:-
Have the return type APIResponse instead of void.
The handler method can have the exception raised as the argument.
Using the exception object to create the APIResponse object.
Put #ResponseBody on the handler method.
Related
I have my Spring error controller, and I need to get the actual exception class so I can print stack trace and other things like that.
This is my error controller
#Controller
public class ErrorController implements org.springframework.boot.web.servlet.error.ErrorController {
#RequestMapping("/error")
public String handleError() {
return "somethingwentwrong";
}
#Override
public String getErrorPath() {
return null;
}
}
I know its not much, but I need the exception object to be able to do some extra handling.
If you wish to execute different code based on the TYPE of the exception thrown, you should look at using #ControllerAdvice along with #ExceptionHandler. Any exceptions that you do not handle with an #ExceptionHandler will then bubble up to the default ErrorController (though you could handle Exception in a handler and then all exceptions will be handled via your custom handler). Something like:
#ControllerAdvice
#RestController
public class CustomResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(MyException.class)
protected ResponseEntity<String> handleMyException(MyException ex) {
// Your code here
}
#ExceptionHandler(Exception.class)
protected ResponseEntity<String> handleException(Exception ex){
// Your code here
}
}
https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-exceptionhandler
No worries guys, I solved the problem, I just had to do some digging into springs built in error controller to get the stacktrace.
I have a basic SpringBoot 2.0.4.RELEASE app. Using Spring Initializer, JPA, embedded Tomcat, Thymeleaf template engine, and package as an executable JAR file.
I have created this class to manage the exceptions
#ControllerAdvice
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
public RestResponseEntityExceptionHandler() {
super();
}
// API
// 400
...
}
But when I try to use it in 1 of my method :
#GetMapping(path = "/getUsers", consumes = "application/json", produces = "application/json")
#ExceptionHandler({RestResponseEntityExceptionHandler.class })
public ResponseEntity<List<User>> testErrors(HttpServletRequest request, #RequestHeader(value = "Authorization") String authHeader) {
...
}
I got this compilation error :
Type mismatch: cannot convert from Class to Class>
If you read the docs for #ExceptionHandler you will see that it is used to mark a method to handle exception(s). Therefore you have to specify which exception(s) it can handle (e.g. #ExceptionHandler(MyException.class) or #ExceptionHandler({MyException.class, MyOtherException.class})). When such an exception occurs in your controller this method gets invoked.
#ControllerAdvice makes the methods defined in the class available to all your controllers.
I don't know what you want to do, but since you extend ResponseEntityExceptionHandler in the #ControllerAdvice class, you could just delete the #ExceptionHandler annotation:
#GetMapping(...)
public ResponseEntity<List<User>> testErrors(...) {
...
}
If you want to handle specific exceptions on your own, you can do it like this:
#ControllerAdvice
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(MyException.class)
public ResponseEntity<?> handleMyException() {
...
}
}
#Controller
public class MyController {
#GetMapping(...)
public ResponseEntity<List<User>> testErrors(...) {
throw new MyException();
}
}
Now handleException() will be called when an error occurs in testErrors().
If you want to handle exceptions only in one controller you can do this:
#Controller
public class MyController {
#GetMapping(...)
public ResponseEntity<List<User>> testErrors(...) {
throw new MyException();
}
#ExceptionHandler(MyException.class)
public ResponseEntity<?> handleMyException() {
...
}
}
It's expected for this not to work correctly.
You're attempting to pass in the RestResponseEntityExceptionHandler as the arguments of the #ExceptionHandler annotation in your test method. This is wrong, as this annotation accepts the type of the exception that gets intercepted.
Also in general it seems that the placement of your #ExceptionHandler annotation seems to be wrong. This are placed within method that reside in the actual exception handling class.
I suggest you have a good read on the way Spring handles exceptions in that manner.
Have a look at this piece of documentation: exception handling in Spring.
I have created a Spring MVC REST service using Bean Validation 1.2 with the following method:
#RequestMapping(value = "/valid")
public String validatedMethod(#Valid ValidObject object) {
}
If object isn't valid, Tomcat informs me that The request sent by the client was syntactically incorrect. and my validatedMethod is never called.
How can I get the message that was defined in the ValidObject bean? Should I use some filter or interceptor?
I know that I can rewrite like below, to get the set of ConstraintViolations from the injected Validator, but the above seems more neat...
#RequestMapping(value = "/valid")
public String validatedMethod(ValidObject object) {
Set<ConstraintViolation<ValidObject>> constraintViolations = validator
.validate(object);
if (constraintViolations.isEmpty()) {
return "valid";
} else {
final StringBuilder message = new StringBuilder();
constraintViolations.forEach((action) -> {
message.append(action.getPropertyPath());
message.append(": ");
message.append(action.getMessage());
});
return message.toString();
}
}
I believe a better way of doing this is using ExceptionHandler.
In your Controller you can write ExceptionHandler to handle different exceptions. Below is the code for the same:
#ExceptionHandler(MethodArgumentNotValidException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
public ValidationFailureResponse validationError(MethodArgumentNotValidException ex) {
BindingResult result = ex.getBindingResult();
final List<FieldError> fieldErrors = result.getFieldErrors();
return new ValidationFailureResponse((FieldError[])(fieldErrors.toArray(new FieldError[fieldErrors.size()])));
}
When you send a bad request to the Controller, the validator throws an exception of type MethodArgumentNotValidException. So the ideal way would be to write an exception handler to specifically handle this exception.
There you can create a beautiful response to tell the user of things which went wrong.
I advocate this, because you have to write this just once and many Controller methods can use it. :)
UPDATE
When you use the #Valid annotation for a method argument in the Controller, the validator is invoked automatically and it tries to validate the object, if the object is invalid, it throws MethodArgumentNotValidException.
If Spring finds an ExceptionHandler method for this exception it will execute the code inside this method.
You just need to make sure that the method above is present in your Controller.
Now there is another case when you have multiple Controllers where you want to validate the method arguments. In this case I suggest you to create a ExceptionResolver class and put this method there. Make your Controllers extend this class and your job is done.
Try this
#RequestMapping(value = "/valid")
public String validatedMethod(#Valid ValidObject object, BindingResult result) {
StringBuilder builder = new StringBuilder();
List<FieldError> errors = result.getFieldErrors();
for (FieldError error : errors ) {
builder.append(error.getField() + " : " + error.getDefaultMessage());
}
return builder.toString();
}
When you use #Valid and doing bad request body Spring handle MethodArgumentNotValidException
You must create special class and extend ResponseEntityExceptionHandler and override handleMethodArgumentNotValid
Example
#ControllerAdvice
public class ControllerExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(UserExistException.class)
public ResponseEntity<Object> handleUserExistException(
UserExistException e, WebRequest request) {
Map<String, Object> body = new LinkedHashMap<>();
body.put("timestamp", LocalDateTime.now());
body.put("status", HttpStatus.BAD_REQUEST.value());
body.put("error", HttpStatus.BAD_REQUEST.getReasonPhrase());
body.put("message", e.getMessage());
body.put("path", request.getDescription(false).replace("uri=", ""));
return new ResponseEntity<>(body, HttpStatus.BAD_REQUEST);
}
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
Map<String, Object> body = new LinkedHashMap<>();
body.put("timestamp", LocalDateTime.now());
body.put("status", HttpStatus.BAD_REQUEST.value());
body.put("error", HttpStatus.BAD_REQUEST.getReasonPhrase());
body.put("path", request.getDescription(false).replace("uri=", ""));
return new ResponseEntity<>(body, headers, status);
}
}
The answer by #dharam works.
For users at Spring v4.3, Here's a nice implementation which uses a Custom Exception class to handle exception by type.
#RestControllerAdvice
public class CustomExceptionClass extends ResponseEntityExceptionHandler{
#ExceptionHandler(value = MethodArgumentNotValidException.class)
public ResponseEntity<Object> handleException(MethodArgumentNotValidException ex, WebRequest req){
// Build your custom response object and access the exception message using ex.getMessage()
}
}
This method will enable handling all #Valid exceptions across all of your #Controller methods in a consolidated way
Let's say I have a repository like:
public interface MyRepository extends PagingAndSortingRepository<MyEntity, String> {
#Query("....")
Page<MyEntity> findByCustomField(#Param("customField") String customField, Pageable pageable);
}
This works great. However, if the client sends a formed request (say, searching on a field that does not exist), then Spring returns the exception as JSON. Revealing the #Query, etc.
// This is OK
http://example.com/data-rest/search/findByCustomField?customField=ABC
// This is also OK because "secondField" is a valid column and is mapped via the Query
http://example.com/data-rest/search/findByCustomField?customField=ABC&sort=secondField
// This throws an exception and sends the exception to the client
http://example.com/data-rest/search/findByCustomField?customField=ABC&sort=blahblah
An example of the exception thrown and sent to client:
{
message:null,
cause: {
message: 'org.hibernate.QueryException: could not resolve property: blahblah...'
}
}
How can I handle those exceptions? Normally, I use the #ExceptionHandler for my MVC controllers but I'm not using a layer between the Data Rest API and the client. Should I?
Thanks.
You could use a global #ExceptionHandler with the #ControllerAdvice annotation. Basically, you define which Exception to handle with #ExceptionHandler within the class with #ControllerAdvice annotation, and then you implement what you want to do when that exception is thrown.
Like this:
#ControllerAdvice(basePackageClasses = RepositoryRestExceptionHandler.class)
public class GlobalExceptionHandler {
#ExceptionHandler({QueryException.class})
public ResponseEntity<Map<String, String>> yourExceptionHandler(QueryException e) {
Map<String, String> response = new HashMap<String, String>();
response.put("message", "Bad Request");
return new ResponseEntity<Map<String, String>>(response, HttpStatus.BAD_REQUEST); //Bad Request example
}
}
See also: https://web.archive.org/web/20170715202138/http://www.ekiras.com/2016/02/how-to-do-exception-handling-in-springboot-rest-application.html
You could use #ControllerAdvice and render the content your way. Here is tutorial if you need know how to work on ControllerAdvice, just remember to return HttpEntity
I implemented a REST application using Spring MVC, Jersey and JAXB.
The client sends a ServiceRequest object which contains information about the request and receives a ServiceResponse object back with information about the response.
ServiceRequest
#XmlRootElement(name = "servicerequest")
public class ServiceRequest{
String serviceName = "AddUser"
public String getServiceName() {
return serviceName;
}
#XmlElement
public void setServiceName(String serviceName) {
this.serviceName = name;
}
}
ServiceResponse
#XmlRootElement(name = "serviceresponse")
public class ServiceResponse {
String responseCode;
public String getResponseCode() {
return responseCode;
}
#XmlElement
public void setResponseCode(String code) {
this.responseCode = name;
}
}
The client makes the call to the service using Jersey
ServiceClient
ClientConfig config = new DefaultClientConfig();
Client client = Client.create(config);
WebResource service = client.resource(RESOURCE_URL);
ServiceRequest request = new ServiceRequest();
ServiceResponse response = service.path("addUser").type(MediaType.APPLICATION_XML)
.accept(MediaType.APPLICATION_XML).entity(request).post(ServiceRequest.class);
On the server side, the service gets the request, handles it and returns a ServiceResponse object.
#RequestMapping(method = RequestMethod.POST,
value = "/addUser",headers="Accept=application/xml")
#ResponseStatus(HttpStatus.OK)
public #ResponseBody ServiceResponse addUser(#RequestBody ServiceRequest request) {
ServiceResponse response = new ServiceResponse();
//Handle request
//service.addUser();
response.setCode("200");
return response;
}
The controller class shown above makes a call to another service class to handle the request (service.addUser()). This service class can raise a number of exceptions.
What i am not sure of is how best to handle them. After googling around, i found that i can use an ExceptionHandler as shown below:
#ExceptionHandler(NullPointerException.class)
#ResponseBody
public String handleException1(NullPointerException ex)
{
return ex.getMessage();
}
I can have the controllers extend a base class that has the exception handler.
A couple of questions regarding the above approach:
Do i have to create one handler for each exception type? There are so many exceptions that i need to handle so is it not possible to just have a generic handler for all exceptions?
As my controller returns an object of type ServiceResponse, what would be the object type that would be returned when an exception occurs?
The client expects to get an object of ServiceResponse type. How does it work if the response is an exception type?
And finally, i noticed that the ExceptionHanlder can only have a return type that is one of the following:
ModelAndView
Model
Map
View
String – interpreted as a view name
void, but only if the method writes directly to the response object
I thought i can create the ServiceResponse object in the ExceptionHandler and set the appropriate code and return the ServiceResponse object. This however is not possible if i cant return a ServiceResponse type in the excpetion handler.
Edit
I tried using the ExceptionHandler using a generic exception class as shown below
#ExceptionHandler(ServiceException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
public #ResponseBody resolveServiceValidationException(ServiceException ex) {
System.out.println("In resolveServiceError");
ServiceResponse = new ServiceResponse();
response.setResponseException(ex.getMessage());
return response;
}
The exception is being caught but the ServiceResponse object is always null. All i get is the following message:
GET http://localhost:8080/myService/services/pingError returned a response status of 400 Bad Request
How can i access the response object when an exception is returned back?
Thanks
#ExceptionHandler annotation can take an array of classes as a parameter, so you can create only one (or more) handlers for multiple exceptions. Change the method signature to take the common ancestor of all handled exception classes as a parameter. Something along the lines:
#ExceptionHandler(value = {NullPointerException.class, AnotherException.class})
#ResponseBody
public String handleException1(Exception ex) {
return ex.getMessage();
}
In your case it'll be String, since you annotated it with #ResponseBody, but you probably want to change the returned http code to some error code. You can do it by adding
#ResponseStatus(value = HttpStatus.WHATEVER_CODE_YOU_WANT, reason = "Your message")`
annotation to your exception handler.
In case when http code returned by the call is greater or equal than 300, jersey client throws UniformInterfaceException. You can catch and handle it. The response type doesn't matter in this case, as the exception is thrown before the conversion.
update
Once you get a hold of UniformInterfaceException and change the exception handler on the server so it returns ServiceResponse as response body, then you can get the reponse using:
ServiceResponse response = uniformInterfaceException.getResponse().getEntity(ServiceResponse.class);