satya posted: " In this spring boot example, we will see primarily two major validation cases – 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 this spring boot example, we will see primarily two major validation cases –
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.
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); }
To apply default validation, we only need to add relevant annotations in proper places. i.e.
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 RecordNotFoundExceptionclass 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 ResponseEntityExceptionHandlerand annotate it with @ControllerAdviceannotation.
ResponseEntityExceptionHandleris a convenient base class for to provide centralized exception handling across all @RequestMapping methods through @ExceptionHandler methods.
@ControllerAdviceis 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.
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).
No comments:
Post a Comment
Note: Only a member of this blog may post a comment.