Validating user input is one of the important tasks while developing any web application. Spring framework provides built-in support for validation of user input.

In the previous article, we learned how to validate the form inputs while developing a Spring MVC web application.

In this article, we will learn how to use @InitBinder annotation and validating the input JSON request using a custom validator class.

We will create a RESTful POST endpoint and validate the JSON input with the custom validator class.

Technologies used in this article:

  • Spring boot version: 2.2.6.RELEASE
  • Java version 1.8

Create a Spring Boot application

Create a spring boot application with required dependencies.

Add spring-boot-starter-web and Lombok(To reduce boilerplate code) dependencies to the pom.xml of the application.

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
	<groupId>org.projectlombok</groupId>
	<artifactId>lombok</artifactId>
	<optional>true</optional>
</dependency>

Create DTO classes

Let us create two DTO classes, Student.java and Address.java.

We will validate the JSON input sent to the REST endpoint and check if the input is valid.

Create a Student.java DTO class and add Getter and Setter annotations.

This class contains the namegrade, and boolean address field to identify if address details available for the student.

The addressDetails field holds the address details.

package com.asbnotebook.dto;

import java.io.Serializable;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class Student implements Serializable{

	private static final long serialVersionUID = 1L;
	
	private String name;
	private Integer grade;
	private Boolean address;
	private Address addressDetails;
}

Create an Address.java DTO class.

package com.asbnotebook.dto;

import java.io.Serializable;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class Address implements Serializable {

	private static final long serialVersionUID = 1L;
	private String street;
	private Integer doorNo;
	private String additionalInfo;
}

Custom error response

This is an optional step. We are creating a customized error JSON format by using the custom response error object.

As we are developing a REST endpoint, we can create a custom meaningful JSON response for validation errors.

Create a custom Error response class

Create an ApiError.java class with the below fields.

We have defined a status field, an error field, a count field to list error count, and a list of validation errors that occurred during input validation.

package com.asbnotebook.dto;

import java.io.Serializable;
import java.util.List;

import org.springframework.http.HttpStatus;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class ApiError implements Serializable {

	private static final long serialVersionUID = 1L;

	private HttpStatus status;
	private String error;
	private Integer count;
	private List<String> errors;
}

Create a custom error handler class

We need to override the default handler method to use our custom error object.

create a global error handler class called ApiErrorHandler.java

The class extends the ResponseEntityExceptionHandler class and overrides the handleMethodArgumentNotValid method and returns a custom error object to the client.

package com.asbnotebook.controller;

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.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

import com.asbnotebook.dto.ApiError;

@ControllerAdvice
public class ApiErrorHandler extends ResponseEntityExceptionHandler {

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

		ApiError apiError = new ApiError();
		apiError.setCount(ex.getBindingResult().getErrorCount());
		apiError.setStatus(HttpStatus.BAD_REQUEST);
		apiError.setError("Validation failed");
		List<String> errors = new ArrayList<>();
		BindingResult bindingResult = ex.getBindingResult();
		bindingResult.getAllErrors().forEach(error -> errors.add(error.getCode()));
		apiError.setErrors(errors);
		return new ResponseEntity<>(apiError, HttpStatus.BAD_REQUEST);
	}

}

Create an input validator class

To implement custom validation, we have to create a custom input validator class.

Create a StudentValidator.java class.

The class should implement the spring validation API’s Validator interface.

We need to implement supports and validate methods of the Validator interface.

The supports method here checks if the input object is of class type Student.

package com.asbnotebook.validator;

import java.util.Locale;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;

import com.asbnotebook.dto.Student;

@Component
public class StudentValidator implements Validator {

	@Autowired
	private MessageSource messageSource;

	@Override
	public boolean supports(Class<?> clazz) {
		return Student.class.equals(clazz);
	}

	@Override
	public void validate(Object target, Errors errors) {
		Student student = (Student) target;
		ValidationUtils.rejectIfEmpty(errors, "name",
				messageSource.getMessage("student.name.error", null, Locale.getDefault()));
		if (student.getGrade() <= 0) {
			errors.rejectValue("grade", messageSource.getMessage("student.grade.error", null, Locale.getDefault()),
					"Student grade should be greater than zero");
		}
		if (null != student.getAddress() && Boolean.TRUE.equals(student.getAddress())
				&& null == student.getAddressDetails()) {
			errors.rejectValue("address", messageSource.getMessage("student.address.error", null, Locale.getDefault()),
					"Student address details should not be empty");
		}
	}
}

The validation method handles all the required input validations.

In the above example, we are validating the student name field by using the spring validation API’s ValidationUtils.

We are validating the grade field by checking if its value is greater than zero.

We are also checking the boolean field address. If a student has an address, the field should be true, and the addressDetails field should contain address details.

Add required error messages

In the above custom validator class, we have used spring’s MessageSource to retrieve the validation error messages.

Create a messages.properties file under /src/main/resources directory.

Add the below properties to the created file.

student.name.error= Student Name field can't be empty.
student.grade.error= Grade should be greater than 0.
student.address.error= Student address details should not be empty.

Create the controller layer

Create a REST controller class called StudentController.java

We have a POST mapping endpoint /student that receives a Student object in the request body.

package com.asbnotebook.controller;

import javax.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import com.asbnotebook.dto.Student;
import com.asbnotebook.validator.StudentValidator;

@RestController("/")
public class StudentController {

	@Autowired
	private StudentValidator studentValidator;

	@InitBinder(value = "student")
	void initStudentValidator(WebDataBinder binder) {
		binder.setValidator(studentValidator);
	}

	@PostMapping("/student")
	public ResponseEntity<Student> saveStudent(@RequestBody @Valid Student student) {
		// Other logic here(Calling the service layer,etc.)
		return new ResponseEntity<>(student, HttpStatus.CREATED);
	}
}

The initStudentValidator method is annotated with @InitBinder annotation. The method registers our custom StudentValidator class to the Webdatabinder as a validator.

The value attribute determines the request parameter, on which the validator should be applied. In our example, the request body parameter with name student is validated by our custom validator class.

Also, notice the @Valid annotation, which makes sure that the validation is performed on the field.

Using multiple InitBinder methods

The Init Binder is applied to all types of inputs if the value attribute is not mentioned.

We can use multiple InitBinder methods inside a controller class. For this, we need to specify the particular request type with the value attribute.

@InitBinder(value = "student")
void initStudentValidator(WebDataBinder binder) {
	binder.setValidator(new StudentValidator());
}

@InitBinder(value = "address")
void initAddressValidator(WebDataBinder binder) {
	binder.setValidator(new AddressValidator());
}

Verify the result

Start the spring boot application.

Send the invalid data to the POST endpoint.

We get the custom error response.

Init binder for input validation spring boot

If we pass the valid data, the validation passes successfully.

Init binder for input validation spring boot.

Conclusion

In this article, we learned how to use InitBinder and custom validator class to validate the input.

We have also learned how to create custom error messages with custom error DTO class.

Example code is available on Github.

Subscribe to my mailing list to get the latest posts on your email. 🙂

Processing…
Success! You’re on the list.

You may also be interested in