ResponsEentityExceptionHandler: handleMethodArgumentNotValid Not Intercepted - java

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

Related

How to not accept additional fields in incoming JSON request body

I have a Spring Boot server that listens on endpoint. I accept #RequestBody as an object:
class Body {
private String name;
}
I want it to accept requests like:
{
"name": "some_name"
}
However, it also accepts:
{
"name": "some_name",
"dummy key":"dummy key value"
}
In that case I want it to throw error. How can I achieve it?
You can do this in the controller when saving:
#PostMapping("/add")
public ResponseEntity<Body> registerUser(#Valid #RequestBody Body saveUser) {
Body createdUser = userService.save(saveUser);
return new ResponseEntity<>(createdUser, HttpStatus.CREATED);
}
When Spring finds an argument annotated with #Valid, it automatically validates the argument and throws an exception if the validation fails.
or you can do this as well:
In the application.properties
spring.jackson.deserialization.fail-on-unknown-properties=true
This helps us to make deserialization fail on unknown properties and throw an exception which we can handle using handleHttpMessageNotReadable
Create controller advice to handle exceptions
#ControllerAdvice
public class CustomExceptionHandler extends ResponseEntityExceptionHandler {
#Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(
HttpMessageNotReadableException ex, HttpHeaders headers,
HttpStatus status, WebRequest request) {
return new ResponseEntity("Your Response Object",
HttpStatus.INTERNAL_SERVER_ERROR);
}
}

Access BindingResult of Spring WebDataBinder without putting in the argument list

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);
}
}

Java Spring - how to handle missing required request parameters

Consider the following mapping:
#RequestMapping(value = "/superDuperPage", method = RequestMethod.GET)
public String superDuperPage(#RequestParam(value = "someParameter", required = true) String parameter)
{
return "somePage";
}
I want to handle the missing parameter case by not adding in required = false. By default, 400 error is returned, but I want to return, let's say, a different page. How can I achieve this?
If a required #RequestParam is not present in the request, Spring will throw a MissingServletRequestParameterException exception. You can define an #ExceptionHandler in the same controller or in a #ControllerAdvice to handle that exception:
#ExceptionHandler(MissingServletRequestParameterException.class)
public void handleMissingParams(MissingServletRequestParameterException ex) {
String name = ex.getParameterName();
System.out.println(name + " parameter is missing");
// Actual exception handling
}
I want to return let's say a different page. How to I achieve this?
As the Spring documentation states:
Much like standard controller methods annotated with a #RequestMapping
annotation, the method arguments and return values of
#ExceptionHandler methods can be flexible. For example, the
HttpServletRequest can be accessed in Servlet environments and the
PortletRequest in Portlet environments. The return type can be a
String, which is interpreted as a view name, a ModelAndView object, a
ResponseEntity, or you can also add the #ResponseBody to have the
method return value converted with message converters and written to
the response stream.
An alternative
If you use the #ControllerAdvice on your class and if it extends the Spring base class ResponseEntityExceptionHandler. A pre-defined function has been created on the base class for this purpose. You have to override it in your handler.
#Override
protected ResponseEntity<Object> handleMissingServletRequestParameter(MissingServletRequestParameterException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
String name = ex.getParameterName();
logger.error(name + " parameter is missing");
return super.handleMissingServletRequestParameter(ex, headers, status, request);
}
This base class is very useful, especially if you want to process the validation errors that the framework creates.
You can do this with Spring 4.1 onwards and Java 8 by leveraging the Optional type. In your example that would mean your #RequestParam String will have now type of Optional<String>.
Take a look at this article for an example showcasing this feature.
Maybe not that relevant, but I came across to a similar need: change the 5xx error to 4xx error for authentication header missing.
The controller is as follows:
#RequestMapping("list")
public ResponseEntity<Object> queryXXX(#RequestHeader(value = "Authorization") String token) {
...
}
When you cURL it without the authorization header you get a 5xx error:
curl --head -X GET "http://localhost:8081/list?xxx=yyy" -H "accept: */*"
HTTP/1.1 500
...
To change it to 401 you can
#ExceptionHandler(org.springframework.web.bind.MissingRequestHeaderException.class)
#ResponseBody
public ResponseEntity<Object> authMissing(org.springframework.web.bind.MissingRequestHeaderException ex) {
log.error(ex.getMessage(), ex);
return IResponse.builder().code(401).message(ex.getMessage()).data(null).build();
}
#Data
public class IResponse<T> implements Serializable {
private Integer code;
private String message = "";
private T data;
...
}
You can verify it by an automation test:
#Test
void testQueryEventListWithoutAuthentication() throws Exception {
val request = get("/list?enrollEndTime=1619176774&enrollStartTime=1619176774&eventEndTime=1619176774&eventStartTime=1619176774");
mockMvc.perform(request).andExpect(status().is4xxClientError());
}

ErrorPageFilter conflicting with ResponseEntityExceptionHandler

As I want to control the output from all endpoints of my application including for endpoints not explicitly defined, I created a simple DefaultController looking like this.
#RestController
public class DefaultController {
#RequestMapping("/**")
public void unmappedRequest(HttpServletRequest request) {
throw new ResourceNotFoundException();
}
}
I also have a #ControllerAdvice error controller that extends ResponseEntityExceptionHandler and overrides all methods from that class, and specifically
#Override
protected ResponseEntity<Object> handleExceptionInternal(Exception ex, Object body, HttpHeaders headers, HttpStatus status, WebRequest request) {
if(HttpStatus.INTERNAL_SERVER_ERROR.equals(status)) {
request.setAttribute("javax.servlet.error.exception", ex, 0);
}
SearchOutput output = new SearchOutput(body);
return new ResponseEntity<>(output, headers, status);
}
I also added a method to handle the ResourceNotFoundException thrown by the default mapper.
#ExceptionHandler(ResourceNotFoundException.class)
public #ResponseBody ResponseEntity<Object> handleResourceNotFound(ResourceNotFoundException ex, WebRequest request) {
HttpHeaders headers = new HttpHeaders();
HttpStatus status = HttpStatus.NOT_FOUND;
ErrorOutput out = new ErrorOutput("Resource not found", status);
return this.handleExceptionInternal(ex, out, headers, status, request);
}
I declared error.whitelabel.enabled:false and added exclude={ErrorMvcAutoConfiguration.class} to the #EnableAutoConfiguration annotation, and still have two problems when running this application in a standalone Tomcat container:
ErrorPageFilter complains about the fact that it Cannot forward to error page for request [/something/not/existing] as the response has already been committed every time the ResourceNotFoundException is thrown;
when the browser (automatically) requests favicon.ico, a Failed to invoke #ExceptionHandler method error for the handleExceptionInternal method is logged, with description Could not find acceptable representation.
Also - is this an acceptable way to deal with non-existing resources?

Spring Bean Validation #Valid handling

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

Categories