#PathVariable validation gives 500 instead of 400 - spring

In the Spring Framework validating the request respond with error 400 (Bad Request), specially when validating the request body and the request fields decorated with the
javax.validation.constraints.* annotations (which specified in JSR 303).
For make it more clear lets go through the example:
I have decorated the fields of the class ContactDetails with #Email and #NotEmpty constraints
class ContactDetails {
#Email
String email;
#NotEmpty
String message;
}
In the controller I used #Valid annotation to make Spring Validator validate the http request body.
#RestController
class NotificationController {
#PostMapping("/sendNotification")
public String sendNotification(#Valid #RequestBody ContactDetails contactDetails) {
...
}
}
If the validation fails, it will trigger a MethodArgumentNotValidException. By default, Spring will translate this exception to a HTTP status 400 (Bad Request).
But for validating the request params or path variables based on Spring documentations I will decorate the controller class with #Validated and just using javax.validation.constraints.* annotations on the parameters and I expect the same result same as validating the request body.
#Validated
#RestController
class NotificationController {
#GetMapping("/getContactDetailsByEmail/{email}")
public ContactDetails findContactDetails(#Email String email) {
...
}
}
In contrast to request body validation a failed validation will trigger a ConstraintViolationException instead of a MethodArgumentNotValidException. Spring does not register a default exception handler for this exception, so it will by default cause a response with HTTP status 500 (Internal Server Error).
I expected to get error 400 for this scenario and I do not know if I missed any thing in my code?
That would be great if any body can help me with this scenario why Spring has different approaches for validating the parameters.

You can create the answer you want by using the fields in the ConstraintViolationException with the following method;
#ExceptionHandler(ConstraintViolationException.class)
protected ResponseEntity<Object> handlePathVariableError(final ConstraintViolationException exception) {
log.error(exception.getMessage(), exception);
final List<SisSubError> subErrors = new ArrayList<>();
exception.getConstraintViolations().forEach(constraintViolation -> subErrors.add(generateSubError(constraintViolation)));
final SisError error = generateErrorWithSubErrors(VALIDATION_ERROR, HttpStatus.BAD_REQUEST, subErrors);
return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
}

In the first case - with argument annotated with #RequestBody - Spring uses RequestResponseBodyMethodProcessor to validate it and throws MethodArgumentNotValidException if validation fails, which is handled later by ResponseEntityExceptionHandler or DefaultHandlerExceptionResolver by translating it into 400 BAD REQUEST http response code.
In the second case Spring is using AOP for method validation with MethodValidationInterceptor class, which throws ConstraintViolationException if validation fails.
But, unlike the first case, Spring doesn't provide a default exception handler for ConstraintViolationException, so it's translated into 500 http response code.
That's why developers should consider creating their own exception handlers for this kind of method-level validation.

Related

Is it possible to trigger validations when calling RestController method from outside?

So far I've got an endpoint which goes as follows:
#PostMapping(path = "/my-endpoint")
public ResponseEntity<Void> method(#PathVariable("id") String id,
#RequestBody #Valid MyClass<MyType> body) {
// custom logic in here
return ResponseEntity.ok().build();
}
When performing the POST request to that endpoint, the validation when the object is wrong is performed properly and 400: Bad Request is shown.
However, now due to some code circumstances I want to trigger that method from outside the RestController and perform the same validations via a Consumer.
The new code goes as follows:
#Bean
public Consumer<Message<String>> consumer(MyController myController) {
message -> myController.method("sampleId", message); // message here is parsed to the class, so the proper type is sent to the controller method.
}
And whenever I check for the myController.method call, the code is always 200: OK, no matter what input is sent.
Is there a way to trigger validations not sent through the REST API?
I suggest to move custom logic from controller to a #Service annotated class first.
Then inject validator #Autowired private Validator validator; and trigger validation.
public void myServiceMethod(MyMessage message) {
Set<ConstraintViolation<MyMessage>> violations = validator.validate(message);
if (!violations.isEmpty()) {
// ...
}
}
https://www.baeldung.com/spring-service-layer-validation

Null check for Request body object

Is it possible to do integration test for null check.
I passed null value.
HttpEntity<Employee> entity = new HttpEntity<Employee>(null, headers);
restTemplate.exchange(url, httpMethod, entity, String.class);
I got the below error.
{"timestamp":"2018-10-06T14:33:52.113+0000","status":400,"error":"Bad Request","message":"Required request body is missing:"}
#RestController
public class EmployeeController {
#PostMapping(value = "/employee/save", produces = "application/json")
public Employee save(#RequestBody Employee employee){
if(employee==null){
throw new RuntimeException("Employee is null");
}
}
}
class Employee {
}
#RequestBody(required=false) Employee employee
please try with required option in #RequestBody.
The problem here is the mapping in spring mvc.
required
Default is true, leading to an exception thrown in case there is no body content. Switch this to false if you prefer null to be passed when the body content is null.
#RequestBody Employee employee
Your method only is processed the request if employee is not null. Then it considered mapping correctly and pass request to this method and handle it. So the check null condition will be needless here.
I am not sure whether it's doable, but I will say it is a bad practice.
According to RFC 7231:
The POST method is used to request that the origin server accept the
representation enclosed in the request as data to be processed by the
target resource.
Since you have annotated your controller PostMapping, there should be request body to server. I don't see the value of writing integration test for null or empty request body.
If we look into the HTTP request structure,
POST /your_url HTTP/1.1
HOST your_host
ContentType ...
ContentLength ...
Body line 1
What's the difference between null and empty?
You can use javax validation framework to check whether #requestbody is null or not
please use below approach: It'll definitely resolve your concern.
Maven Dependency:
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.1.0.Final</version>
</dependency>
#RestController
public class EmployeeController {
#PostMapping(value = "/employee/save", produces = "application/json")
public Employee save(#NotNull #RequestBody Employee employee){
}
}
You are getting this error message because #RequestBody annotation has default value and you have to send valid request to your method.
You don't need to null check inside of method for request object because #RequestBody has required field and this field's default value is true. If you check the inside of #RequestBody interface you are going to see what I am saying. Also you can see the below;
If you set #RequestBody(required = false) in method's parameter you are not going to get error message, but you need to check your request object is null or not... If you don't check-out of object is null when you use that object you will get "Null Pointer Excepiton"...
your choise...
good luck
You can use #NonNull either with the #RequestBody annotation. Or, even better if you want the variables of the Object to always be non-null after the constructor is called, you can use this annotation in the class itself when defining the variables/attributes.

#RequestBody not restricting to POJO type and BindingResult hasErrors always false

I was not experiencing this problem early in development but just noticed that this was happening when debugging another problem. This happens on all REST endpoints, but below is an example:
#RestController
#RequestMapping("/editlisting")
public class EditParkingSpaceListingController {
#Autowired
ParkingSpaceRepository parkingSpaceRepository;
#Autowired
ParkingSpaceListingRepository parkingSpaceListingRepository;
#RequestMapping(method = RequestMethod.PUT)
public ResponseEntity<String> editParking(#RequestBody ParkingSpaceListingClient pslc, BindingResult result) {
if (result.hasErrors()) {
return new ResponseEntity<String>("", HttpStatus.BAD_REQUEST);
}
// Code to save pslc data to database.
Now, if I send an HTTP request with the body as
{ }
I get a 200 response and when I check MongoDB, there is a new empty document in the collection. If I send an empty body with no brackets, as expected it will return 400. If I send a body with random garbage data that does not exist in the POJO, BindingResult does not seem to pick up the error and a new blank document is still created.
You need to follow the below steps for the input document validations:
(1) Add the javax.validation package constraints (like #NotNull, #Size, etc..) to your ParkingSpaceListingClient bean class.
(2) Add #Validated annotation to your controller method, to capture the validation errors into BindingResult object.
You can look here for more details on Input Validations.

Databinding in the controller using spring throws exception

I have a controller with method parameter as model say
public Response create(Customer customer){
}
Customer model :customer model looks like
#JsonTypeInfo( use = JsonTypeInfo.Id.NAME,property = "type")
#JsonSubTypes({#Type(value = Config.class, name = "IPC")})
public class Customer(){
private String type; }
From swagger UI if i send type as IPC its works fine, but any other value than IPC throws an 400 exception while binding.How can i catch this exception inside controller
try to use the #ExceptionHandler annotation
The documentation of Spring 4 (http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#mvc-ann-exceptionhandler) states that
The HandlerExceptionResolver interface and the
SimpleMappingExceptionResolver implementations allow you to map
Exceptions to specific views declaratively along with some optional
Java logic before forwarding to those views. However, in some cases,
especially when relying on #ResponseBody methods rather than on view
resolution, it may be more convenient to directly set the status of
the response and optionally write error content to the body of the
response.
You can do that with #ExceptionHandler methods. When declared within a
controller such methods apply to exceptions raised by #RequestMapping
methods of that controller (or any of its sub-classes). You can also
declare an #ExceptionHandler method within an #ControllerAdvice class
in which case it handles exceptions from #RequestMapping methods from
many controllers. Below is an example of a controller-local
#ExceptionHandler method:
So, in your controller you can have a method like this
#ExceptionHandler(MethodArgumentNotValidException.class)
public String handleArgumentNotValid(MethodArgumentNotValidException e, ModelMap map, HttpServletRequest request) {
List<ObjectError> errors = e.getBindingResult() .getAllErrors();
//you can get the exception e,
//you can get the request
//handle whatever you want the then return to view
return "your view that you will handle the exception";
}

RequestMapping conversion exception

I have a Spring RequestMapping that is taking a JSON structure and converting it to a POJO. If I send the route a malformed JSON structure, I get back a "400 Bad Request" but what I'd like to receive is an error message stating why it was a bad request. I have a Validator also checking the JSON structure, but it seems that it's not called if the structure is unable to be converted. Is there a way in which I can access the built in HttpMessageConverter errors or exceptions?
I think you can use a ResponseEntityExceptionHandler for this, by overriding the handleHttpMessageNotReadable method.
From: 17.11.3 Handling Standard Spring MVC Exceptions:
If you prefer to write error content via #ExceptionHandler methods you can extend ResponseEntityExceptionHandler instead. This is a convenient base for #ControllerAdvice classes providing an #ExceptionHandler method to handle standard Spring MVC exceptions and return ResponseEntity. That allows you to customize the response and write error content with message converters. See the ResponseEntityExceptionHandler javadocs for more details.
An example usage of the ResponseEntityExceptionHandler can be found here: http://www.jayway.com/2013/02/03/improve-your-spring-rest-api-part-iii/
Otherwise you can find more spring mvc exception handling approaches here:
https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc
If your needs aren't too complex you might just want to use a SimpleMappingExceptionResolver
ResponseEntityExceptionHandler is used to identify the Spring MVC specific exceptions & to implement a common error response handling strategy for them.
It consists of many methods for example:
handleMissingServletRequestParameter > handle if invalid request params
handleHttpMessageNotReadable > handles if invalid request body
handleExceptionInternal > A single place to customize the response body of all exception types.
The below mentioned code overrides handleExceptionInternal method
#ControllerAdvice
public class JavaWebExeptionHandler extends ResponseEntityExceptionHandler {
public class ExpnDetails {
public int expnCode;
public String expnMessage;
}
// A single place to customize the response body of all exception types.
#Override
protected ResponseEntity<ExpnDetails> handleExceptionInternal(Exception ex, Object body,
HttpHeaders headers,HttpStatus status, WebRequest request) {
return new ResponseEntity<Object>(new ExpnDetails(status.value(), ex.getMessage()), status);
}
}
So all the Spring MVC specific exception are handled at one place and ExpnDetails object is sent as response body which consists of a expnCode & expnMessage.
Output:
{
"expnCode": 415,
"expnMessage": "Content type 'application/json1' not supported"
}
{
"expnCode": 400,
"expnMessage": "JSON parse error: Unexpected character ('"
' (code 34)): was expecting comma to separate Object entries; nested exception is com.fasterxml.jackson.core.JsonParseException: Unexpected character ('
"' (code 34)): was expecting comma to separate Object entries at [Source: (PushbackInputStream); line: 3, column: 4]"
}
Note this solution also helps in hiding container(ex. tomcat server) details to appear in response error when request fails.

Resources