One standard that we need to follow when we are building microservices and REST APIs is, we need to perform validations on the input data that we receive from the client applications.
Because of the Global Exception handler that we implemented, whenever a user sends some wrong data, business exceptions will be generated and he will get a response saying resource not found like that.
But you are unnecessarily firing a DB query with an invalid data, we should straight away reject such invalid data from our REST APIs.
As a developer we need to make sure we are doing enough validations on the input data that we receive to our REST APIs.
Dependency we need to add
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
Any time, whenever we want to perform validations first, we need to make sure we have added relevant dependency inside the pom.xml like spring-boot-starter-validation.
This dependency has all the annotations and libraries that will help us to enforce the validations on the input data that we receive from the clients.
Adding Validations
Let’s go to DTO classes. Why these classes ? DTO classes are the ones that are receiving the data from the client.
Whatever data that we receive from the clients will get converted into DTO classes. So these DTOs are going to hold the data that we are going to receive from clients, which means my Spring Boot has to perform the validations on the data stored inside the object of these DTO classes.
→ Since we want to enforce some validations on the input received from the clients, we need to make sure we are mentioning all our validation requirements with the help of annotations inside the Spring Boot framework.
@NotEmpty annotation
So whenever we are mentioning this @NotEmpty, we are making that field mandatory.
@NotEmpty
private String name;
So whenever someone is trying to send this name value, it has to be mandatory, like it should not be empty or null value.
@NotEmpty
private String email;
Whenever a validation fails due to this @NotEmpty annotation that we mentioned here, we want to throw a custom message to the client application so that they are clear about which data is wrong.
@NotEmpty(message = "Name can not be a null or empty")
private String name;
@NotEmpty(message = "Email address can not be a null or empty")
private String email;
For throwing the customer message, we need to invoke the message parameter against this annotation and inside this message parameter, you can specify the message.
@Size annotation
Now I want to enforce one more validation on top of my name field with the help of @Size annotation. We can mention what is the minimum length of value that we are going to accept, similarly, we can mention what is the maximum value that we are going to accept.
@NotEmpty(message = "Name can not be a null or empty")
@Size(min = 5, max = 30, message = "The length of the customer name should be between 5 and 30")
private String name;
If this validation fails, I’m going to throw a message saying that the length of the customer name should be between 5 and 30.
@Email annotation
Now on top of email, I want to enforce email format related validations, so that’s why I need to invoke @Email annotation.
@NotEmpty(message = "Email address can not be a null or empty")
@Email(message = "Email address should be a valid value")
private String email;
Suppose if I try to forget mentioning @ Symbol inside my email value, then I’ll get these validation error message.
@Pattern annotation
Now coming to the mobile number, we want to make sure that we always receive a mobile number in numeric format and the numeric value also should be exactly a ten digit number and it should not be like nine digit or 11 digit. I want exactly a ten digit numeric value.
For these kinds of requirements, we can use an annotation which is @Pattern. With the help of @Pattern annotation, we can invoke regex expression and to this regex expression, we can pass what is the expression that we want to follow.
@Pattern(regexp = "(^$|[0-9]{10})", message = "Mobile no must be 10 digits")
private String mobileNumber;
The above pattern will make sure that I’m accepting only numeric values with exactly 10 digits.
Classes after adding validations
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CustomerDto {
@NotEmpty(message = "Name can not be a null or empty")
@Size(min = 5, max = 30, message = "The length should be between 5 and 30")
private String name;
@NotEmpty(message = "Email address can not be a null or empty")
@Email(message = "Email address should be a valid value")
private String email;
@Pattern(regexp = "(^$|[0-9]{10})", message = "Mobile no be 10 digits")
private String mobileNumber;
private AccountsDto accountsDto;
}
Next, we can go to the AccountsDto class and add validations there as well.
@Data
@NoArgsConstructor
@AllArgsConstructor
public class AccountsDto {
@NotEmpty(message = "Account Number cannot be null or empty")
@Pattern(regexp = "(^$|[0-9]{10})", message = "Account no be 10 digits")
private Long accountNumber;
@NotEmpty(message = "AccountType can not be a null or empty")
private String accountType;
@NotEmpty(message = "BranchAddress can not be a null or empty")
private String branchAddress;
}
@Validated annotation
We need to go to the place where we are trying to use these AccountsDto and CustomerDto classes, it will be in the Controller class.
Controller class is the one that handles the incoming requests, this is where we get the data from the clients, either in the request body or query param.
→ The very first annotation that I want to mention here is @Validated on top of my Controller class.
This annotation will tell my Spring Boot framework to perform validations on all the REST APIs that I have defined inside the Controller class.
@Valid annotation
As a next step, we need to mention an annotation which is @Valid just before the @RequestBody.
@PostMapping("/create")
public ResponseEntity<ResponseDto> createAccount(
@Valid @RequestBody CustomerDto customerDto)
{
service.createAccount(customerDto);
…
…
}
We need to mention @Valid just before the @RequestBody annotation to communicate to the Spring Boot framework to perform all the validations that we mentioned inside the customerDto class.
For the request parameters, we can mention the validation just before the parameter itself.
@GetMapping("/fetch")
public ResponseEntity<CustomerDto> fetchAccountDetails(
@RequestParam
@Pattern(regexp="(^$|[0-9]{10})",message = "Mobile must be 10 digits")
String mobileNumber)
{
CustomerDto customerDto = service.fetchAccount(mobileNumber);
return ResponseEntity.status(HttpStatus.OK).body(customerDto);
}
@DeleteMapping("/delete")
public ResponseEntity<ResponseDto> deleteAccountDetails(
@RequestParam
@Pattern(regexp="(^$|[0-9]{10})",message = "Mobile no must be 10 digits")
String mobileNumber)
{
boolean isDeleted = service.deleteAccount(mobileNumber);
…
…
}
For the request parameters, with the help of @Pattern, I’m accepting a regex expression that will make sure that I’m receiving only a ten digit numeric value.
Sending Validation responses to client
So now we have mentioned all the input validations that we want to perform. As a next step we should let Spring Boot framework know what it has to do whenever these validations fail.
It knows what validations it has to perform, the framework also knows what messages it has to throw to the end user or client application, but it does not know how to send that inside the error response or inside the body of the response.
Extend GlobalExceptionHandler class with the name ResponseEntityExceptionHandler.
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
…
…
}
After extending the class, there is a method inside the parent class which is handleMethodArgumentsNotValid(). We need to override this method inside our GlobalExceptionHandler.
@Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException ex, HttpHeaders headers,
HttpStatusCode status, WebRequest request)
{
Map<String, String> validationErrors = new HashMap<>();
List<ObjectError> validationErrorList = ex.getBindingResult().getAllErrors();
validationErrorList.forEach((error) -> {
String fieldName = ((FieldError) error).getField();
String validationMsg = error.getDefaultMessage();
validationErrors.put(fieldName, validationMsg);
});
return new ResponseEntity<>(validationErrors, HttpStatus.BAD_REQUEST);
}
We need to write a small logic inside this method which will process all the validation exceptions that failed and the same we need to send as part of the response with the help of this ResponseEntity object.
First I’m trying to create an empty Map with the name validationErrors and it is going to hold the key and value of type string.
Next, using this exception parameter ex, I’m invoking getBindingResult().getAllErrors(). So this will give me a list of all validation errors failed in the input data that I receive.
Using this list, I’m trying to iterate one by one and from each error I’m trying to get what is the field where the validation failed and what is the validation message related to the field.
I’m populating these details into the HashMap validationErrors. Once we populate all the validationErrors, I’m trying to send the validationErrors as part of ResponseEntity with a status as HttpStatus.BAD_REQUEST.
Build and Deploy
The application is started at port number 8080.
Using Postman, you can invoke the REST APIs.