Showing posts with label validation. Show all posts

Constraints validation for user inputs (javax.validation.constraints)

User input validation is common part of any application. JBoss developers made it easy to validate inputs using Java Bean Validation framework.

Bean Validation 2.0 (http://beanvalidation.orgJSR 380)
We will be using Bean Validation framework 2.0 for the example. Its certified implementation is Hibernate Validator 6.0.1.Final or above. Framework 2.0 require Java 8 or higher version.

Source code (User.java)
import javax.validation.constraints.Email;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Size;

/**
 * @author javaQuery
 * @since 2018-02-24
 * @github: https://github.com/javaquery/Examples
 */
public class User {

    @NotEmpty(message = "firstName can not be empty")
    @Size(min = 2, max = 20, message = "firstName length must be between 2 and 20")
    private String firstName;

    @NotEmpty(message = "lastName can not be empty")
    @Size(min = 2, max = 20, message = "lastName length must be between 2 and 20")
    private String lastName;

    @NotEmpty(message = "nickNames can not be empty")
    private List<@Size(min = 2, message = "nickName length must be greater than 2") String> nickNames;

    @Email
    private String email;

    @NotEmpty(message = "password can not be empty")
    @Size(min = 6, message = "password length must be at least 6 character")
    private String password;

    // getter-setter
}
  • @NotEmpty - We are using it to check string can not be empty. Can be used with collection, map or array.
  • @Size - We are using it to check the length of string. Can be used with collection, map or array.
  • @Email - The string has to be a well-formed email address.
There are other annotations like @NotNull, @Min, @Max@Past, @Future, @PastOrPresent, @FutureOrPresent, etc... Complete list of annotations is available on Bean Validation specification.

Source code (ValidatorFactoryExample.java)
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;

/**
 * @author javaQuery
 * @since 2018-02-24
 * @github: https://github.com/javaquery/Examples
 */
public class ValidatorFactoryExample {

    public static void main(String[] args) {
        User user = new User();
        user.setFirstName("Vicky");
        user.setEmail("not-an-email");
        user.setNickNames(Arrays.asList("vicks", "v"));
        user.setPassword("1234567");
        
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        Validator validator = factory.getValidator();
        /* validate object and get constraints failed */
        Set<ConstraintViolation<User>> violations = validator.validate(user);
        
        for (ConstraintViolation<User> violation : violations) {
            System.err.println(violation.getMessage());
        }
    }
}

Output
nick name lenght must be greater than 2
lastName can not be empty
must be a well-formed email address

Other frameworks like Spring triggers validation using annotation so you don't have to validate bean on your own like we did using ValidatorFactory.

Similar
Passing and validating RequestParam in spring-boot rest api

Passing and validating RequestParam in spring-boot rest api

In my previous article Creating first rest api in spring-boot we learned how you can create rest api. Now lets learn how you can pass parameters to api and validate it. In this example I'm going to use code from my previous article or you can head to Spring Tutorials page.

@RequestParam
Annotation which indicates that a method parameter should be bound to a web request parameter.

Source code (HelloWorldController.java)
import javax.validation.constraints.Size;

import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author javaQuery
 * @since 2018-01-31
 * @github https://github.com/javaquery/spring-boot-examples
 */
@RestController
@RequestMapping("/api")
@Validated
public class HelloWorldController {
 
 /**
  * This will send Hello {name-from-parameter} in response with HTTP status code 200.
  * @since 2018-02-01
  * @param firstname
  * @param lastname
  * @return
  */
   @GetMapping("/hello")
   public ResponseEntity<?> sayHello(
     @RequestParam @Size(min= 1, max = 5, message = "firstname length must be between 1 and 5") String firstname,
     @RequestParam String middlename,
     @RequestParam(required = false) String lastname){
    /* check lastname value */
    lastname = lastname != null ? lastname : "{lastname-is-optional}";
    return ResponseEntity.ok("Hello " + firstname + " " + middlename + " " + lastname);
   }
}
Request parameter syntax
Using @RequestParam spring rest api accept parameters from the request. You can also use various annotation from package javax.validation.constraints with @RequestParam.
@RequestParam <data_type> <variable_name>;

Lets quickly understand annotations used in HelloWorldController.java. As we already discussed few annotations in Creating first rest api in spring-boot so we will talk about new annotations only.

  • @Validated - To perform validation on each method of controller if any.
  • @RequestParam - To accept web request parameter in variable. (Note: All variable annotated with @RequestParam are compulsory/mandatory for request, until you set required = false @RequestParam(required = false) for that parameter)
Request parameter variations
@RequestParam @Size(min= 1, max = 5 , message = "firstname length must be between 1 and 5") String firstname
- @Size of package javax.validation.constraints is used to validate length of parameter in request and will throw ConstraintViolationException if data is not valid. You can read more about Constraints validation for user inputs.
@RequestParam String middlename
- Accept mandatory parameter in variable middlename. If parameter is not present in request then spring will throw MissingServletRequestParameterException.
@RequestParam(required = false) String lastname
- Accept optional parameter in variable lastname.

Exception Handling
For exception org.springframework.web.bind.MissingServletRequestParameterException and javax.validation.ConstraintViolationException, spring will send 500 (Internal Server Error) in response so we'll catch those exception and send customized response.

Source code (RestExceptionHandler.java)
Used to catch exception thrown.
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;

import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

/**
 * @author javaQuery
 * @since2018-02-01
 */
@Order(Ordered.HIGHEST_PRECEDENCE)
@ControllerAdvice()
public class RestExceptionHandler extends ResponseEntityExceptionHandler{
 
   @Override
   protected ResponseEntity<Object> handleMissingServletRequestParameter(MissingServletRequestParameterException ex, HttpHeaders headers,
     HttpStatus status, WebRequest request) {
    String error = ex.getParameterName() + " parameter is missing.";
    return new ResponseEntity<Object>(new ErrorResponse<>(Arrays.asList(error)), HttpStatus.BAD_REQUEST);
   }
 
   /**
    * Exception thrown when {@link org.springframework.validation.annotation.Validated} is used in controller.
    * @param ex
    * @param request
    * @return
    */
   @ExceptionHandler(ConstraintViolationException.class)
   protected ResponseEntity<?> handleConstraintViolationException(ConstraintViolationException ex, HttpServletRequest request) {
    try {
     List<String> messages = ex.getConstraintViolations().stream().map(ConstraintViolation::getMessage).collect(Collectors.toList());
     return new ResponseEntity<>(new ErrorResponse<>(messages), HttpStatus.BAD_REQUEST);
    } catch (Exception e) {
     return new ResponseEntity<>(new ErrorResponse<>(Arrays.asList(ex.getMessage())), HttpStatus.INTERNAL_SERVER_ERROR);
    }
   }
}
Source code (ErrorResponse.java)
Used to wrap the error message for response.
import java.util.ArrayList;
import java.util.List;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;

/**
 * @author javaQuery
 * @since 2018-02-01
 * 
 * @param <T>
 */
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(Include.NON_NULL)
public class ErrorResponse<T> {
   private List<T> errors = new ArrayList<>(1);
 
   public ErrorResponse(List<T> errors) {
    this.errors = errors;
   }
 
   public List<T> getErrors() {
    return errors;
   }
}

Request/Response
cURL GET http://localhost:8080/api/hello
-code 400
-body {"errors":["firstname parameter is missing."]}
cURL GET http://localhost:8080/api/hello?firstname=&middlename=v
-code 400
-body {"errors":["firstname length must be between 1 and 5"]}
cURL GET http://localhost:8080/api/hello?firstname=vicky&middlename=v
-code 200
-body Hello vicky v {lastname-is-optional}
cURL GET http://localhost:8080/api/hello?firstname=vicky&middlename=v&lastname=thakor
-code 200
-body Hello vicky v thakor