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
Related
I'd like to do custom exception handling for a REST API.
This is the code I have.
Controller Endpoint
#PatchMapping(value="/customer/name", produces = "application/json")
public ResponseEntity<Customer> updateName(
#RequestParam(value="customerId") Long customerId,
#RequestParam(value="name") String name){
customerRepository.updateCustomerName(customerId, name);
Customer updatedCustomer = customerRepository.findCustomer(customerId);
return new ResponseEntity<Customer>(updatedCustomer, HttpStatus.OK);
}
Custom Exception Handling Class
#ControllerAdvice
public class CustomRestExceptionHandler extends ResponseEntityExceptionHandler{
#ExceptionHandler(value = {Exception.class})
public ResponseEntity<Object> handleAll(Exception ex, WebRequest request) {
return new ResponseEntity<>(
ex, new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
If I force an error inside the endpoint method (such as adding the null pointer exception below), it will correctly enter the handleAll method and return the custom error.
String x = null;
int y = x.length();
But, if instead of that, I generate the error by going to Postman and pass a String instead of a Long in the customerId parameter, it doesn't enter the custom error class.
In fact, it never enters the controller method.
How to make the custom error class catch and display custom error for that as well?
thanks
try to override handleMethodArgumentTypeMismatch
#ExceptionHandler({MethodArgumentTypeMismatchException.class})
public ResponseEntity<Object> handleMethodArgumentTypeMismatch( MethodArgumentTypeMismatchException ex, WebRequest request) {
return ResponseEntity
}
I am trying to use Spring validation with a controller interface generated by swagger-codegen. The swagger code generation supplies an abstract class for a controller. Our controller implements the codegen class and provides the actual logic. I would like to access the BindingResult in my controller methods, but swagger-codegen does not generate that parameter in its interface. Is there any way to get ahold of the BindingResults object other than specifying it as a parameter?
To make this more concrete, the codegen makes the endpoint like this (noisy code removed):
#RequestMapping(value = "/api/repository/v1/datasets",
produces = { "application/json" },
consumes = { "application/json" },
method = RequestMethod.POST)
default ResponseEntity<JobModel> createDataset(#Valid #RequestBody DatasetRequestModel dataset) {
...
}
We implement a controller with the usual binder setup like:
#InitBinder
protected void initBinder(final WebDataBinder binder) {
binder.addValidators(requestValidator)
}
but within the endpoint, we have no way to get the BindingResult since it has to match the signature of the codegen entry:
public ResponseEntity<StudySummaryModel> createStudy(#Valid #RequestBody StudyRequestModel studyRequest) {
...
}
I think the most straightforward solution may be to skip using WebDataBinder. Instead, I can have each controller endpoint call validators directly.
I found another approach besides hand coding the validation; using an #ControllerAdvice class that extends ResponseEntityExceptionHandler.
There is a nice example here: Spring Validation Example
Here is my code based on that example that formats the error:
#ControllerAdvice
public class ApiValidationExceptionHandler extends ResponseEntityExceptionHandler {
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException ex,
HttpHeaders headers,
HttpStatus status,
WebRequest request
) {
BindingResult bindingResult = ex.getBindingResult();
List<String> errorDetails = bindingResult
.getFieldErrors()
.stream()
.map(err -> err.getCode() + " error on '" + err.getObjectName() + "': " + err.getDefaultMessage())
.collect(toList());
ErrorModel errorModel = new ErrorModel()
.message("Validation errors - see error details")
.errorDetail(errorDetails);
return new ResponseEntity<>(errorModel, HttpStatus.BAD_REQUEST);
}
}
My controller has the following method:
#RequestMapping(method = RequestMethod.POST)
#ResponseStatus(HttpStatus.CREATED)
public void save(#RequestBody #Valid final User resource) {
createInternal(resource);
}
Because of the #Valid before the resource parameter, I expect it to be intercepted by the following exception handler when I pass in a NULL into a nullable=false on my #Column of my entity,
#Override
protected final ResponseEntity<Object> handleMethodArgumentNotValid(final MethodArgumentNotValidException e,
final HttpHeaders headers,
final HttpStatus status,
final WebRequest request) {
log.info("Bad Request: {}", ex.getMessage());
log.debug("Bad Request: ", ex);
...
return handleExceptionInternal(e, dto, headers, HttpStatus.BAD_REQUEST, request);
}
But it seems I can only handle it this way instead:
#ExceptionHandler(value = { ConstraintViolationException.class,
DataIntegrityViolationException.class })
public final ResponseEntity<Object> handleBadRequest(final RuntimeException e,
final WebRequest request) {
...
return handleExceptionInternal(ex, apiError, new HttpHeaders(), HttpStatus.BAD_REQUEST, request);
}
Why isn't the handleMethodArgumentNotValid exception handler picking it up like it should?
Before your 'handleMethodArgumentNotValid' has a chance to fire, DefaultHandlerExceptionResolver handles this
or if you want to handle declare , #ExceptionHandler(MethodArgumentNotValidException.class)
An #RequestBody method parameter can be annotated with #Valid, in
which case it will be validated using the configured Validator
instance. When using the MVC namespace or the MVC Java config, a
JSR-303 validator is configured automatically assuming a JSR-303
implementation is available on the classpath.
Just like with #ModelAttribute parameters, an Errors argument can be
used to examine the errors. If such an argument is not declared, a
MethodArgumentNotValidException will be raised. The exception is
handled in the DefaultHandlerExceptionResolver, which sends a 400
error back to the client. Before your 'handleMethodArgumentNotValid'
has a chance to fire, this is handndf
http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-ann-requestbody
Why isn't the handleMethodArgumentNotValid exception handler picking
it up like it should?
Spring container (during start up) scans through all of the controller(s) methods annotated with #RequestMapping and #ExceptionHandler.
Later, when the request comes with an url, the controller method will be identified using handlermapping, then injects all the required dependencies (controller method arguments) like Model, HttpRequest, etc.. and delegates the call the controller method to serve the input request.
Since your handleMethodArgumentNotValid is not annotated with either #RequestMapping or #ExceptionHandler, Spring container can't recognise this method.
#ExceptionHandler({ ConstraintViolationException.class })
public ResponseEntity<> handleConstraintViolation(ConstraintViolationException ex,
WebRequest request) {
// error handeling
return new ResponseEntity<>(ex.getMessage(), HttpStatus.BAD_REQUEST);
}
More details at https://www.baeldung.com/global-error-handler-in-a-spring-rest-api
In a Spring RestController I have an input validation of the RequestBody simply by annotating the corresponding method parameter as #Valid or #Validated. Some other validations can only be performed after some processing of the incoming data. My question is, what type of exceptions should I use, so that it resembles the exception thrown by the #Valid annotation, and how do I construct this exception from the validation result. Here is an example:
#RequestMapping(method = RequestMethod.POST)
public ResponseEntity<?> createOrder(#RequestBody #Validated(InputChecks.class) Order order) {
// Some processing of the Order goes here
Set<ConstraintViolation<Order>> violations = validator.validate(order, FinalChecks.class);
// What to do now with the validation errors?
orders.put(order);
HttpHeaders headers = new HttpHeaders();
headers.setLocation(ServletUriComponentsBuilder.fromCurrentRequest().path("/" + order.getId()).build().toUri());
return new ResponseEntity<>(null, headers, HttpStatus.CREATED);
}
To me the simplest way looks like validating the object with an errors object, and use it in a MethodArgumentNotValidException.
#RequestMapping(method = RequestMethod.POST)
public ResponseEntity<?> createOrder(#RequestBody #Validated(InputChecks.class) Order order)
throws NoSuchMethodException, SecurityException, MethodArgumentNotValidException {
// Some processing of the Order goes here
SpringValidatorAdapter v = new SpringValidatorAdapter(validator);
BeanPropertyBindingResult errors = new BeanPropertyBindingResult(order, "order");
v.validate(order, errors, FinalChecks.class);
if (errors.hasErrors()) {
throw new MethodArgumentNotValidException(
new MethodParameter(this.getClass().getDeclaredMethod("createOrder", Order.class), 0),
errors);
}
orders.put(order);
HttpHeaders headers = new HttpHeaders();
headers.setLocation(ServletUriComponentsBuilder.fromCurrentRequest().path("/" + order.getId()).build().toUri());
return new ResponseEntity<>(null, headers, HttpStatus.CREATED);
}
This way the errors found during the second validation step have exactly the same structure as the errors found during the input validation on the #validated parameters.
For handling validation errors in the second run, i can think of three different approaches. First, you can extract validation error messages from Set of ConstraintViolations and then return an appropriate HTTP response, say 400 Bad Request, with validation error messages as the response body:
Set<ConstraintViolation<Order>> violations = validator.validate(order, FinalChecks.class);
if (!violations.isEmpty()) {
Set<String> validationMessages = violations
.stream()
.map(ConstraintViolation::getMessage)
.collect(Collectors.toSet());
return ResponseEntity.badRequest().body(validationMessages);
}
// the happy path
This approach is suitable for situations when the double validation is a requirement for a few controllers. Otherwise, it's better to throw a brand new Exception or reuse spring related exceptions, say MethodArgumentNotValidException, and define a ControllerAdvice that handle them universally:
Set<ConstraintViolation<Order>> violations = validator.validate(order, FinalChecks.class);
if (!violations.isEmpty()) {
throw new ValidationException(violations);
}
And the controller advice:
#ControllerAdvice
public class ValidationControllerAdvice {
#ExceptionHandler(ValidationException.class)
public ResponseEntity handleValidtionErrors(ValidationException ex) {
return ResponseEntity.badRequest().body(ex.getViolations().stream()...);
}
}
You can also throw one of spring exceptions like MethodArgumentNotValidException. In order to do so, you need to convert the Set of ConstraintViolations to an instance of BindingResult and pass it to the MethodArgumentNotValidException's constructor.
I am developing a project using Spring REST web services, where I need to show graceful error messages when an exception/error occurs. I followed this tutorial (http://www.javacodegeeks.com/2013/02/exception-handling-for-rest-with-spring-3-2.html) for exception handling using SpringREST. I get the proper output when there is no exception/error i.e. in form of an XML. The issue arises when an exception occurs. Here is part of the code base where an exception occurs if I do not pass the testId in
localhost:8080/test?testId=
The class outputs a response in form of a XML, so when an exception occurs, instead of showing the error message as figure 1 below, it shows error message as figure 2. If I do "View Page Source", I get the correct exception message (as figure 1). But I need the exception message directly. Could anyone, please suggest a solution?
#RequestMapping(value = "/test",
method = RequestMethod.GET,
produces = "application/xml")
public #ResponseBody String testResource(
#RequestParam(value="testId", required=true) String testId)
throws CustomRestException{
if (testId == null || testId.equals(""))
{
LOG.error( "testResource(): " + TestUtilsException.NULL_TEST_ID_ERROR_MSG );
//The error message is: The test Id is required and cannot be null or empty
throw new CustomRestException(TestUtilsException.NULL_TEST_ID_ERROR_MSG);
}
}
Figure 1
Figure 2
Other helper classes:
#ControllerAdvice
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
public RestResponseEntityExceptionHandler() {
super();
}
#ExceptionHandler(value = { CustomRestException.class })
#ResponseBody
protected ResponseEntity<Object> handleNotFound(final RuntimeException ex, final WebRequest request) {
final String bodyOfResponse = ex.getMessage();
return handleExceptionInternal(ex, bodyOfResponse, new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR, request);
}
}
public class CustomRestException extends RuntimeException {
public CustomRestException() {
super();
}
public CustomRestException(final String message, final Throwable cause) {
super(message, cause);
}
public CustomRestException(final String message) {
super(message);
}
public CustomRestException(final Throwable cause) {
super(cause);
}
}
The #ControllerAdvice approach should work, although I don't think there's any need for the base class - you can just use #ExceptionHandler with Spring 4. But you are returning a response body that cannot be converted to Xml (it's a plain String), so you are getting an empty response and probably a 405 instead of a 500. If you want an Xml response you have to provide a body that can be converted (or else provide an HttpMessageConverter that can do it).
Consider doing this.
public class BaseController{
#ExceptionHandler(value = { CustomRestException.class })
protected #ResponseBody ResponseEntity<ErrorResponse > handleNotFound(final RuntimeException ex, final WebRequest request) {
System.out.println("is executed in handler");
final String bodyOfResponse = ex.getMessage();
return new ResponseEntity<ErrorResponse >(new ErrorResponse (bodyOfResponse), null, HttpStatus.NOT_FOUND);
}
}
In your controller do this.
#Controller
public class HomeController extends BaseController {//Your code here}
And create this class.
#XmlRootElement
public class ErrorResponse {
public String error;
}
Finally add to your class the following code
if (testId == null || testId.equals(""))
{
throw new CustomRestException("DD");
}
That will create an XML response as follows.
This XML file does not appear to have any style information associated
with it. The document tree is shown below.
<successResponse> <error>DD</error> </successResponse>
This will handle all the exception an is not needed to add #ControllerAdvice, that seems need to add their own MessageConverters that is why the answer is not converted to XML, I read that here.
I added ,produces = "application/xml" and remove it, and is still working as I think you want. Please let me know if this was useful.