Custom annotation with Spring

In client-server application when a client sends a request to server to insert something into database or when server fetches some data from database and set this data to model class, sometimes we need to validate this data against some rules like data of a specific property should not be null or empty, length of a particular property should be less than something or greater than something.

In other words, we need to implement server side validation against the data which is received by server. There can be many ways to implement server side validation like we fetch each value which are coming to server and validate value of each property against some business rules. This is something in which we have to write some boilerplate code.

In order to remove boilerplate code, Spring provides many annotations to implement server side validations. These annotations can be applied on class level, method level and on field level. To check how to use these annotations see this basic example of spring.

But annotations which are provided by spring is not enough to implement validation against business rules. There are some kind of validations for which spring doesn’t provide any annotations so we again have to write boilerplate code to validate those inputs.

To overcome it we can create custom annotation to validate data.

In today’s post I will describe how to implement custom annotation in Spring boot and how we can apply custom annotation over the bean and validate bean property or method against custom annotation.

Following is the step by step process to create custom annotation and validate data against it:

Create spring boot project and add following dependency in pom.xml. To know how to create spring boot project follow this.

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

Create a simple class annotated with @interface annotation as below:

package com.threadminions.annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

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

/***
 * Custom annotations {@code SupportedValues}

 * Used for specifying list of supported values for a particular property or method of a {@code Bean}
 * @author THREADMINIONS
 */

@Retention(RetentionPolicy.RUNTIME) //Annotation will work at RUNTIME
@Target(value = {ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER}) //Annotation can be applied either on METHOD or on FIELD
@Constraint(validatedBy = CustomValidator.class) //CustomValidator class will validate the values
public @interface SupportedValues
{
	String message() default "Values are not supported";

	String[] values();

	Class<?>[] groups() default {}; //Required by Constraint

	Class<? extends Payload>[] payload() default {}; //Required by Constraint
}

Here @interface defines that class is going to be an Annotation. Above class contains following things:

  • @Retention – It describes at what time annotation will be available to the class. It can be RUNTIME, CLASS, SOURCE etc. See java doc.
  • @Target – It describes where this annotation can be used. In this example, this can be used at method or at field level. See java doc.
  • @Constraint – It describes which class will validate data against this annotation. See java doc.
  • SupportedValues is the name of annotation. It contains two property as below:
    • message – describes the attribute of this annotation with default value “Values are not supported”.
    • values – array of String value

This is all how to define custom annotation in Spring. Now let’s use this custom annotation in a class.

Create a class let say ConfigProp as below:

package com.threadminions.model;

import org.springframework.stereotype.Component;

import com.threadminions.annotations.SupportedValues;

@Component
public class ConfigProp {

	@SupportedValues(message = "Invalid values found for dbType", values = {"ORACLE", "MYSQL", "SQL"})
	private String dbType;

	public String getDbType() {
		return dbType;
	}

	public void setDbType(String dbType) {
		this.dbType = dbType;
	}
}

In this class, there is a property dbType

  • message – when validation will fail for this property then this message will be an error message.
  • values – An array of String {“MYSQL”, “ORACLE”, “SQL”} describes that dbType can support/contain only these values otherwise error will be shown.

Define validation class CustomValidator to validate data against annotation as below:

package com.threadminions.annotations;

import java.util.Arrays;
import java.util.List;

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

/***
 * Custom validator class to validate custom annotation {@code SupportedValues}
 *
 * @author THREADMINIONS
 *
 */
public class CustomValidator implements ConstraintValidator<SupportedValues, String>{

	public String message;
	public String[] values;

	@Override
	public void initialize(SupportedValues supportedValues)
	{
		this.message = supportedValues.message();
		this.values = supportedValues.values();
	}

	@Override
	public boolean isValid(String value, ConstraintValidatorContext arg1)
	{
		List<String> lstValues = Arrays.asList(values);

		return value != null && !value.isEmpty() && lstValues.contains(value);
	}

}

This class implements ConstraintValidator interface which provides two method as below:

  • initialize() – It is used to fetch the data of annotation attributes and can be set in local parameters.
  • isValid() – In this, first parameter is the run time value which is given to property and it will be validated against the annotation. In this method, we can give implementation about how you want to validate its data and return a Boolean value accordingly.

Create a Main class to test the annotation as below:

package com.threadminions;

import java.util.Set;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.ValidatorFactory;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import com.threadminions.model.ConfigProp;

@SpringBootApplication
public class SpringBootMain implements CommandLineRunner
{
	public static void main(String[] args) {
		SpringApplication.run(SpringBootMain.class, args);
	}

	@Override
	public void run(String... args) throws Exception
	{

		String dbType = "POSTGRES";

		ConfigProp property = new ConfigProp();
		property.setDbType(dbType);//setting unsupported values here

		//Inbuilt class that is used to validate data
		ValidatorFactory validator = Validation.buildDefaultValidatorFactory();
		Set<ConstraintViolation<ConfigProp>> validationErrors = validator.getValidator().validate(property);
		if(!validationErrors.isEmpty()) //If there are some errors then print those
		{
			for(ConstraintViolation<ConfigProp> invalidObj : validationErrors)
			{
				System.out.println(invalidObj.getMessage());
			}
		}

	}

}

In above example, we created an object of ConfigProp class and set dbType property to “POSTGRES” which is invalid and then we are validating it.

Now it’s time to run the class so let’s run it and it will show following output:

annotation

As output is showing a message “Invalid values found for dbType”.

This is all about how to create custom annotation and how to use it.

Click here for source code…..!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s