Issue
I'm attempting to use Spring's validation of two @RequestParam
s, with an @ControllerAdvice
catching the exceptions thrown by the framework when a parameter is missing, and returning a 400 error with the missing parameter.
So, my code looks like:
@RestController
@Validated
public class FooController {
@RequestMapping(value = "/foo", method = RequestMethod.GET)
@ResponseBody
public Foo getFoo(@RequestParam LocalDate dateFrom, @RequestParam LocalDate dateTo) {
// Do stuff
}
}
@ControllerAdvice
public class ExceptionController {
@ExceptionHandler(value = {MissingServletRequestParameterException.class})
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
@ResponseBody
public ErrorResponse handleMissingParameterException(MissingServletRequestParameterException ex) {
return new ErrorResponse(ex.getMessage());
}
}
This works perfectly if I miss a single parameter - I get a nice JSON response that looks like:
{
"reason": "Required LocalDate parameter 'dateFrom' is not present"
}
with a 400 status.
However if I miss both parameters, I get the same error response as above - i.e. it's only reporting the first missing parameter, where I'd prefer it if I can list all of them.
Looking at the method of the exception, it seems like it only intends to handle a single parameter - it has methods getParameterName()
and getParameterType()
in the singular.
Is there any way I can get Spring to report all validation errors in a single exception to improve the experience for the client?
Solution
You can set @RequestParam(required = false)
and then use @NotNull
.
If a parameter is null
then a ConstraintViolationException
will be thrown. To handle it just declare an @ExceptionHandler
method. Check the code snippet below
@RestController
@Validated
public class FooController {
@RequestMapping(value = "/foo", method = RequestMethod.GET)
@ResponseBody
public Foo getFoo(
@RequestParam (required = false)
@NotNull(message = "Required LocalDate parameter dateFrom is not present")
LocalDate dateFrom,
@RequestParam (required = false)
@NotNull(message = "Required LocalDate parameter dateTo is not present")
LocalDate dateTo)
) {
// Do stuff
}
}
@ControllerAdvice
public class ExceptionController {
@ExceptionHandler({ConstraintViolationException.class})
public ResponseEntity<List<ErrorResponseDTO>> exceptionHandler(ConstraintViolationException e) {
List<ErrorResponseDTO> errors = new ArrayList<>(e.getConstraintViolations().size());
e.getConstraintViolations().forEach(violation -> errors.add(new ErrorResponseDTO(violation.getMessage())));
return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST);
}
}
Outcome will be like this:
[
{
"reason": "Required LocalDate parameter dateFrom is not present"
},
{
"reason": "Required LocalDate parameter dateTo is not present"
}
]
Answered By - Ivan Polovyi