Part 1 – Spring Rest Input Validations

Input Validation

Spring Rest Input Validations

All You Need To Know About Bean Validation With Spring Boot

Most of the time we need to create the validations on input model of the rest. To customize the validation, we will use Hibernate Validator, which is one of the implementations of the bean validation API.
We can get Hibernate Validator for free when we use Spring Boot Starter Web. So, we can get started with implementing the validations.

Step 1 – Implementing Validations on the Bean

Let’s add a few validations to the Employee bean. We are using @NotNull anf @Positive annotations to specify the minimum length and also a message when a validation error occurs.

public class EmployeeDTO {

 private Long id;
 
 @NotNull(message = "First name must not be empty")
 private String firstName;

 @NotNull(message = "Last name must not be empty")
 private String lastName;
 
 @Positive(message = "Salary must be positive")
 private Long salary;
}

Step 2 – Enabling Validation on the Resource

We can enable the validation by adding @Valid in addition to @RequestBody into our rest controller.

@PostMapping
public ResponseEntity<Object> addEmployee(@Valid @RequestBody EmployeeDTO employeeDTO) {
  System.out.println(employeeDTO);
  EmployeeDO employeeDO = mapper.map(employeeDTO, EmployeeDO.class);
  service.addEmployee(employeeDO);
  return new ResponseEntity<>(HttpStatus.CREATED);
}

That’s it. When you execute a request with attributes not matching the constraint, you get a 400 Bad Request status back.
Request :
Open Postman and new request of POST with URL as http://localhost:8088/employees and paste below request in raw type and select the type as JSON (application/json)

{
"firstName" : null,
"lastName" : null,
"salary" : "-10000"
}

Part 1-1
Response :
We will only get the HTTP response status as 400 Bad Request with below response message.

{
  "timestamp": "2018-11-11T05:39:11.118+0000",
  "status": 400,
  "error": "Bad Request",
  "errors": [
    {
      "codes": [
        "Positive.employeeDTO.salary",
        "Positive.salary",
        "Positive.java.lang.Long",
        "Positive"
      ],
      "arguments": [
        {
          "codes": [
            "employeeDTO.salary",
            "salary"
          ],
          "arguments": null,
          "defaultMessage": "salary",
          "code": "salary"
        }
      ],
      "defaultMessage": "Salary must be positive",
      "objectName": "employeeDTO",
      "field": "salary",
      "rejectedValue": -10000,
      "bindingFailure": false,
      "code": "Positive"
    },
    {
      "codes": [
        "NotNull.employeeDTO.firstName",
        "NotNull.firstName",
        "NotNull.java.lang.String",
        "NotNull"
      ],
      "arguments": [
        {
          "codes": [
            "employeeDTO.firstName",
            "firstName"
          ],
          "arguments": null,
          "defaultMessage": "firstName",
          "code": "firstName"
        }
      ],
      "defaultMessage": "First name must not be empty",
      "objectName": "employeeDTO",
      "field": "firstName",
      "rejectedValue": null,
      "bindingFailure": false,
      "code": "NotNull"
    },
    {
      "codes": [
        "NotNull.employeeDTO.lastName",
        "NotNull.lastName",
        "NotNull.java.lang.String",
        "NotNull"
      ],
      "arguments": [
        {
          "codes": [
            "employeeDTO.lastName",
            "lastName"
          ],
          "arguments": null,
          "defaultMessage": "lastName",
          "code": "lastName"
        }
      ],
      "defaultMessage": "Last name must not be empty",
      "objectName": "employeeDTO",
      "field": "lastName",
      "rejectedValue": null,
      "bindingFailure": false,
      "code": "NotNull"
    }
  ],
  "message": "Validation failed for object='employeeDTO'. Error count: 3",
  "path": "/employees"
}

What’s the problem with this approach ?
Client will not understand the reason behind the 400 Bad Request, as spring is passing unnecessary error message information in response body and client will not be able to identify the exact cause of the error.

Step 4 – Customizing Validation Response

Whenever hibernate validation fails it throws MethodArgumentNotValidException. This exception is catched by handleException method of ResponseEntityExceptionHandler class. In this method it checks for the instance of the MethodArgumentNotValidException and calls the handleMethodArgumentNotValid method of class. We will need to override handleMethodArgumentNotValid method to customize the error response.

@ExceptionHandler({
 HttpRequestMethodNotSupportedException.class,
 HttpMediaTypeNotSupportedException.class,
 HttpMediaTypeNotAcceptableException.class,
 MissingPathVariableException.class,
 MissingServletRequestParameterException.class,
 ServletRequestBindingException.class,
 ConversionNotSupportedException.class,
 TypeMismatchException.class,
 HttpMessageNotReadableException.class,
 HttpMessageNotWritableException.class,
 MethodArgumentNotValidException.class,
 MissingServletRequestPartException.class,
 BindException.class,
 NoHandlerFoundException.class,
 AsyncRequestTimeoutException.class
 })
@Nullable
public final ResponseEntity<Object> handleException(Exception ex, WebRequest request) throws Exception {
 if (ex instanceof MethodArgumentNotValidException) {
  HttpStatus status = HttpStatus.BAD_REQUEST;
  return handleMethodArgumentNotValid((MethodArgumentNotValidException) ex, headers, status, request);
 }
}


Lets create a response bean to handle the cause of the error. This bean will hold the timestamp, error message and details of the error message.

public class ErrorDetails {
 
   private Date timestamp;
   
   private String message;
   
   private String details;
   
   public ErrorDetails(Date timestamp, String message, String details) {
     super();
     this.timestamp = timestamp;
     this.message = message;
     this.details = details;
   } 
}


Lets add a new class with @RestControllerAdvice to override the handleMethodArgumentNotValid() method to catch the MethodArgumentNotValidException of ResponseEntityExceptionHandler.

@RestControllerAdvice
public class EmployeeExceptionHandler extends ResponseEntityExceptionHandler {

    @Override
    protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex,
                                                                  HttpHeaders headers, HttpStatus status, WebRequest request) {

        String errorMessage = ex.getBindingResult().getAllErrors().stream()
                .map(error -> error.getDefaultMessage())
                .collect(Collectors.joining(","));

        ErrorDetails errorDetails = new ErrorDetails(new Date(), "Constraints Validation Failed", errorMessage);

        return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST);
    }

    @ExceptionHandler(value = EntityNotFoundException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ErrorDetails handleExceptionHandler(EntityNotFoundException e) {
        return new ErrorDetails(new Date(), "Entity Not Found", e.getMessage());
    }
}

Now we will hit the url with same request and now we can see that we are getting a readable bean where in we are getting the actual causes of BAD REQUEST 400. You can find the postman collection of this request at – CRUD Rest Example Postman Collection  :
{
“firstName” : null,
“lastName” : null,
“email” : “onlyfullstack@gmail.com”,
“salary” : “10000”
}
Part 1-2

 

Available annotations to use

In above example, we used only few annotations such as @NotEmpty and @Positive. There are more such annotations to validate request data. Check them out when needed.
 
@AssertFalse
The annotated element must be false.

@AssertTrue
The annotated element must be true.
 
@DecimalMax
The annotated element must be a number whose value must be lower or equal to the specified maximum.
 
@DecimalMin
The annotated element must be a number whose value must be higher or equal to the specified minimum.
 
@Future
The annotated element must be an instant, date or time in the future.
 
@Max
The annotated element must be a number whose value must be lower or equal to the specified maximum.
 
@Min
The annotated element must be a number whose value must be higher or equal to the specified minimum.
 
@Negative
The annotated element must be a strictly negative number.
 
@NotBlank
The annotated element must not be null and must contain at least one non-whitespace character.
 
@NotEmpty
The annotated element must not be null nor empty.
 
@NotNull
The annotated element must not be null.
 
@Null
The annotated element must be null.
 
@Pattern
The annotated CharSequence must match the specified regular expression.
 
@Positive
The annotated element must be a strictly positive number.
 
@Size
The annotated element size must be between the specified boundaries (included).

Source Code

Download source code of Spring Rest Advance Topics from below git repository :
spring-rest-advance-topics

Spring Rest Advanced Tutorial

https://www.onlyfullstack.com/spring-rest-advanced-tutorial/

Lets go to our next tutorial where we will discuss below point

2. Spring Rest Documentation with Swagger – In this post we will go through below topics,
– What is Swagger ?
– How to create Rest API documentation with Swagger ?
– How to share the documentation with clients ?

Blog URL – Part 2 – Spring Rest Advance : Rest Documentation with Swagger 2