Spring Boot

Spring Validator – Spring Boot Validation Example

In the last article, we have seen how to create a simple spring boot rest example.

In this tutorial, we will take a look at the spring validator and how to create a custom validator for the bean validation.

To demonstrate the validators concept in spring boot, we will take a look at a few examples.

Why Validators required?

Let’s take an example of a User entity class.

@Entity
@Table(name = "user_details")
public class User {
	
	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	private int user_id;
	
	private String user_name;
	
	private String password;
	
	private String email;
	
	private String contact_number;

The user entity has many properties (columns in the database) like id, user name, password, email, and contact number.

Therefore, how you will make sure that the client shouldn’t provide empty value for those properties.

For those types of validation, we can use annotations provided by Hibernate validators (JSR-380).

Hibernate validators in Spring Boot

Hibernate validators offer a few annotations that can be used to a bean property for validation purposes.

Spring boot by default add hibernate validator’s dependency and configure it on the classpath.

Therefore, we don’t have to write any code to add hibernate validator into our project.

So, Let’s change the above User class and add a constraint called @NotEmpty.

package com.codedelay.rest.entity;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.validation.constraints.NotEmpty;

import com.codedelay.rest.validation.ContactNumberValidation;
import com.codedelay.rest.validation.Password;

@Entity
@Table(name = "user_details")
public class User {
	
	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	private int user_id;
	
	@NotEmpty(message = "Please enter a user name")
	private String user_name;
	
	@NotEmpty(message = "Please enter the password")
	private String password;
	
	@NotEmpty(message = "Please enter the email")
	private String email;
	
	@NotEmpty(message = "Please provide contact number")
	private String contact_number;

	public int getUser_id() {
		return user_id;
	}

	public void setUser_id(int user_id) {
		this.user_id = user_id;
	}

	public String getUser_name() {
		return user_name;
	}

	public void setUser_name(String user_name) {
		this.user_name = user_name;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public String getEmail() {
		return email;
	}

	public void setEmail(String email) {
		this.email = email;
	}

	public String getContact_number() {
		return contact_number;
	}

	public void setContact_number(String contact_number) {
		this.contact_number = contact_number;
	}
}

The above @NotEmpty will make sure that the client will provide some value for those bean properties.

If the client doesn’t provide a value, the client will get 500 internal error.

The above annotation doesn’t enable hibernate bean validation automatically.

How to enable Hibernate Bean Validation

There are two ways to enable bean validation.

  1. You can add @Valid (javax.validation) before request body.
  2. You can add @Validated above class Name

Enable Bean Validation using @Valid example (UserController.java)

@PostMapping("/add")
	@ResponseStatus(HttpStatus.CREATED)
	public User addUser(@Valid @RequestBody User user) {
		return mService.addUser(user);
	}

Now if you trigger HTTP POST request, then MethodArgumentNotValidException will occur and HTTP 400 Bad Request result will be returned by Spring.

In the above scenario, spring won’t provide an error message along 400 code.

To provide a clear error message let’s override handleMethodArgumentNotValid in GlobalExceptionHandler.

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

		Map<String, Object> body = new LinkedHashMap<>();
		body.put("timestamp", new Date());
		body.put("status", status.value());
		
		//Get all errors
        List<String> errors = ex.getBindingResult()
                .getFieldErrors()
                .stream()
                .map(x -> x.getDefaultMessage())
                .collect(Collectors.toList());

        body.put("errors", errors);
        return new ResponseEntity<>(body, headers, status);
	}

Now let’s see the error message when we provide the empty email address

{
    "timestamp": "2019-07-31T09:17:30.679+0000",
    "status": 400,
    "errors": [
        "{user.name.invalid}"
    ]
}
hibernate validator demo

Enable Bean Validation using @Validated at class Level (UserController.java)

Instead of adding @Valid before the request body, you can also provide @Validated at the class level.

@RestController
@RequestMapping("/api/user")
@Validated
public class UserController {

In the case of validated failed, spring will throw ConstraintViolationException.

Let’s this exception in the GlobalExceptionHandle class.

@ExceptionHandler(ConstraintViolationException.class)
	public void handleConstraintViolationError(HttpServletResponse response) throws IOException {
		response.sendError(HttpStatus.BAD_REQUEST.value());
	}

Now let’s try to add a new User without providing his contact number. Spring will throw 500 internal error (BAD_REQUEST).

Caused by: javax.validation.ConstraintViolationException: Validation failed for classes [com.codedelay.rest.entity.User] during persist time for groups [javax.validation.groups.Default, ]\nList of constraint violations:[\n\tConstraintViolationImpl{interpolatedMessage='Please provide contact number', propertyPath=contact_number, rootBeanClass=class com.codedelay.rest.entity.User, messageTemplate='Please provide contact number'}\n]\r\n\tat org.hibernate.cfg.beanvalidation.BeanValidationEventListener.validate(BeanValidationEventListener.java:140)\r\n\tat org.hibernate.cfg.beanvalidation.BeanValidationEventListener.onPreInsert(BeanValidationEventListener.java:80)

From the above logs, it is clear that when we don’t provide a required value (@NonEmpty) then we get 500 internal error with a clear exception ConstraintViolationException with the value “Please provide contact number”.

Let’s add a few more Hibernate Validators annotation to bean validation.

@NotEmpty(message = "Please provide contact number")
	@Length(min = 7, max = 10)
	private String contact_number;

As you can see we have added a @Length annotation.

@Length annotation will validate that the user should provide a minimum 7 digit number and max 10 digits number.

Let’s hit the below request.

HTTP POST http://localhost:8080/api/user/add
{
“user_name”: “root”,
“password”: “linux”,
“email”: “root@root.com”,
“contact_number”: “898898”
}

Output:

Caused by: javax.validation.ConstraintViolationException: Validation failed for classes [com.codedelay.rest.entity.User] during persist time for groups [javax.validation.groups.Default, ]\nList of constraint violations:[\n\tConstraintViolationImpl{interpolatedMessage='length must be between 7 and 10', propertyPath=contact_number, rootBeanClass=class com.codedelay.rest.entity.User, messageTemplate='{org.hibernate.validator.constraints.Length.message}'}\n]\r\n\tat org.hibernate.cfg.beanvalidation.BeanValidationEventListener.validate(BeanValidationEventListener.java:140)

It is clear that user will get 500 internal error if contact_number length is less than 7 digits or more than 10 digits.

In the above example, message = “Please provide contact number” is an error message.

You can also define an error message in the application.properties file.

@NotEmpty(message = "Please enter the email")
	@Email(message = "{user.email.invalid}")
	private String email;

application.properties file

user.name.invalid=Invalid Username
user.email.invalid=Invalid Email

To summarize, let’s look at the complete User.java file.

package com.codedelay.rest.entity;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Size;

import org.hibernate.validator.constraints.Length;

@Entity
@Table(name = "user_details")
public class User {
	
	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	private int user_id;
	
	@NotEmpty(message = "Please enter a user name")
	@Size(max = 18, min = 6, message = "{user.name.invalid}")
	private String user_name;
	
	@NotEmpty(message = "Please enter the password")
	private String password;
	
	@NotEmpty(message = "Please enter the email")
	@Email(message = "{user.email.invalid}")
	private String email;
	
	@NotEmpty(message = "Please provide contact number")
	@Length(min = 7, max = 10)
	private String contact_number;

	public int getUser_id() {
		return user_id;
	}

	public void setUser_id(int user_id) {
		this.user_id = user_id;
	}

	public String getUser_name() {
		return user_name;
	}

	public void setUser_name(String user_name) {
		this.user_name = user_name;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public String getEmail() {
		return email;
	}

	public void setEmail(String email) {
		this.email = email;
	}

	public String getContact_number() {
		return contact_number;
	}

	public void setContact_number(String contact_number) {
		this.contact_number = contact_number;
	}
}

How to add a Custom Validator

Password validator hibernate
Password validator

If you look at the above post request, I have provided “lin ux” in the password field.

Although the above request was successful, you could observe that the password has space in between.

Let’s write a custom validator to check if the password has any space in between and throw an error if space is present.

Steps to create a custom validator

1. Create an annotation Password by providing its definition in Password.java

package com.codedelay.rest.validation;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import javax.validation.Constraint;
import javax.validation.Payload;

@Target({FIELD})
@Retention(RUNTIME)
@Constraint(validatedBy = PasswordValidator.class)
@Documented
public @interface Password {
	String message() default "Invalid password";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

2. Create a validator PasswordValidator.java that implements ConstraintValidator.

package com.codedelay.rest.validation;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class PasswordValidator implements ConstraintValidator<Password, String>{

	@Override
	public boolean isValid(String password, ConstraintValidatorContext context) {
		return (password != null) &&
				(!password.contains(" "));
	}
}

3. Add @Password on password property of User class.

@NotEmpty(message = "Please enter the password")
	@Password
	private String password;

4. Now test the above changes, spring will throw an exception.

 "timestamp": "2019-07-31T09:38:03.943+0000",
    "status": 500,
    "error": "Internal Server Error",

Conclusion

In this article, we discussed the importance of hibernate validators and how to create a custom validator.

Click to rate this post!
[Total: 0 Average: 0]

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.