In this spring boot example, we will see primarily two major validation cases –

  1. HTTP POST /employees and request body does not contain valid values or some fields are missing. It will return HTTP status code 400 with proper message in response body.
  2. HTTP GET /employees/{id} and INVALID ID is sent in request. It will return HTTP status code 404 with proper message in response body.

Create REST APIs and model classes

EmployeeRESTController.java

 @PostMapping(value = "/employees") public ResponseEntity<EmployeeVO> addEmployee (@RequestBody EmployeeVO employee) {     EmployeeDB.addEmployee(employee);     return new ResponseEntity<EmployeeVO>(employee, HttpStatus.OK); }   @GetMapping(value = "/employees/{id}")  public ResponseEntity<EmployeeVO> getEmployeeById (@PathVariable("id") int id) {     EmployeeVO employee = EmployeeDB.getEmployeeById(id);           if(employee == null) {          throw new RecordNotFoundException("Invalid employee id : " + id);     }     return new ResponseEntity<EmployeeVO>(employee, HttpStatus.OK); } 

EmployeeVO.java

 @XmlRootElement(name = "employee") @XmlAccessorType(XmlAccessType.FIELD) public class EmployeeVO extends ResourceSupport implements Serializable {     private Integer employeeId;     private String firstName;     private String lastName;     private String email;       public EmployeeVO(Integer id, String firstName, String lastName, String email) {         super();         this.employeeId = id;         this.firstName = firstName;         this.lastName = lastName;         this.email = email;     }       public EmployeeVO() {     }           //Removed setter/getter for readability } 

REST request validation

Default spring validation support

To apply default validation, we only need to add relevant annotations in proper places. i.e.

  1. Annotate model class with required validation specific annotations such as @NotEmpty@Email etc.
 @XmlRootElement(name = "employee") @XmlAccessorType(XmlAccessType.FIELD) public class EmployeeVO extends ResourceSupport implements Serializable {     private static final long serialVersionUID = 1L;           public EmployeeVO(Integer id, String firstName, String lastName, String email) {         super();         this.employeeId = id;         this.firstName = firstName;         this.lastName = lastName;         this.email = email;     }       public EmployeeVO() {     }       private Integer employeeId;       @NotEmpty(message = "first name must not be empty")     private String firstName;       @NotEmpty(message = "last name must not be empty")     private String lastName;       @NotEmpty(message = "email must not be empty")     @Email(message = "email should be a valid email")     private String email;           //Removed setter/getter for readability } 

2. Enable validation of request body by @Valid annotation

 @PostMapping(value = "/employees") public ResponseEntity<EmployeeVO> addEmployee (@Valid @RequestBody EmployeeVO employee) {     EmployeeDB.addEmployee(employee);     return new ResponseEntity<EmployeeVO>(employee, HttpStatus.OK); } 

Default spring validation works and provide information overload about error, and that's why we should customize it according to our application's need.

We shall provide only required error information with very clear wordings. Extra information is also not suggested.

It is always a good advise to create exceptions that are meaningful and describe the problem well enough.

One way is to create separate classes to denote specific business use-case failure and return them when that use-case fail.

e.g. I have created RecordNotFoundException class for all bunch scenarios where a resource is requested by it's ID, and resource is not found in the system.

RecordNotFoundException.java

 package com.howtodoinjava.demo.exception;   import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ResponseStatus;   @ResponseStatus(HttpStatus.NOT_FOUND) public class RecordNotFoundException extends RuntimeException  {     public RecordNotFoundException(String exception) {         super(exception);     } } 

Similarly, I have wrote an special class which will be returned for all failure cases. Having consistent error message structure for all APIs, help the API consumers to write more robust code.

ErrorResponse.java

 import java.util.List; import javax.xml.bind.annotation.XmlRootElement;   @XmlRootElement(name = "error") public class ErrorResponse  {     public ErrorResponse(String message, List<String> details) {         super();         this.message = message;         this.details = details;     }       //General error message about nature of error     private String message;       //Specific errors in API request processing     private List<String> details;       //Getter and setters } 

Custom ExceptionHandler

Now add one class extending ResponseEntityExceptionHandler and annotate it with @ControllerAdvice annotation.

ResponseEntityExceptionHandler is a convenient base class for to provide centralized exception handling across all @RequestMapping methods through @ExceptionHandler methods. 

@ControllerAdvice is more for enabling auto-scanning and configuration at application start-up.

CustomExceptionHandler.java

 package com.howtodoinjava.demo.exception;   import java.util.ArrayList; import java.util.List;   import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.validation.ObjectError; import org.springframework.web.bind.MethodArgumentNotValidException; 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;   @SuppressWarnings({"unchecked","rawtypes"}) @ControllerAdvice public class CustomExceptionHandler extends ResponseEntityExceptionHandler  {       @ExceptionHandler(Exception.class)     public final ResponseEntity<Object> handleAllExceptions(Exception ex, WebRequest request) {         List<String> details = new ArrayList<>();         details.add(ex.getLocalizedMessage());         ErrorResponse error = new ErrorResponse("Server Error", details);         return new ResponseEntity(error, HttpStatus.INTERNAL_SERVER_ERROR);     }         @ExceptionHandler(RecordNotFoundException.class)     public final ResponseEntity<Object> handleUserNotFoundException(RecordNotFoundException ex, WebRequest request) {         List<String> details = new ArrayList<>();         details.add(ex.getLocalizedMessage());         ErrorResponse error = new ErrorResponse("Record Not Found", details);         return new ResponseEntity(error, HttpStatus.NOT_FOUND);     }         @Override     protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {         List<String> details = new ArrayList<>();         for(ObjectError error : ex.getBindingResult().getAllErrors()) {             details.add(error.getDefaultMessage());         }         ErrorResponse error = new ErrorResponse("Validation Failed", details);         return new ResponseEntity(error, HttpStatus.BAD_REQUEST);     } } 

Above class handles multiple exceptions including RecordNotFoundException; and it also handle request validation errors in @RequestBody annotated object. Let's see how it works.

Demo

1) HTTP GET /employees/1 [VALID]

 HTTP Status : 200   {     "employeeId": 1,     "firstName": "John",     "lastName": "Wick",     "email": "howtodoinjava@gmail.com", } 

2) HTTP GET /employees/23 [INVALID]

 HTTP Status : 404   {     "message": "Record Not Found",     "details": [         "Invalid employee id : 23"     ] } 

3) HTTP POST /employees [INVALID]

 Request  {     "lastName": "Bill",     "email": "ibill@gmail.com" } 
 Response  HTTP Status : 400   {     "message": "Validation Failed",     "details": [         "first name must not be empty"     ] } 

4) HTTP POST /employees [INVALID]

 Request   {     "email": "ibill@gmail.com" } 
 Response   HTTP Status : 400   {     "message": "Validation Failed",     "details": [         "last name must not be empty",         "first name must not be empty"     ] } 

5) HTTP POST /employees [INVALID]

 Request   {     "firstName":"Lokesh",     "email": "ibill_gmail.com" //invalid email in request } 
 Response   HTTP Status : 400   {     "message": "Validation Failed",     "details": [         "last name must not be empty",         "email should be a valid email"     ] } 

REST request validation annotations

In above example, we used only few annotations such as @NotEmpty and @Email. There are more such annotations to validate request data. Check them out when needed.

ANNOTATION USAGE
@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).

This free site is ad-supported. Learn more