Hello,

I would present you an example concerning the annotation in JAVA: validation POJO with annotation.

Brief presentation
Introduced with Java SE 5, the annotations are more increasingly used in the development of applications. Annotations provide informations about a class and they have no direct effect on the annotated code. Annotations can be preserved at runtime (RetentionPolicy.RUNTIME) or are only available at development time, during the compile (RetentionPolicy.SOURCE).

There are standard annotations and Java allows the definition of custom annotations.

For example, the following standard annotations are often used in Java:
@Override:
This annotation added to a method, notifies the Java compiler to check if the annotated method really overrides a method of an interface or the extended class.

@Override
public String toString(){
	return this.getClass().getName()
			+ "[id="+id
			+ ", name="+name
			+ ", price="+price
			+ ", numberOfRead="+numberOfRead
			+ ", lastReadDateTime="+lastReadDateTime
			+ ", authors="+authors
			+ "]";
}

@Deprecated
This annotation added to a field, method or constructor, indicates that the annotated element should not be used anymore. It is possible to add this annotation to a class, however, this doesn’t deprecate automatically all its fields and methods.

@Target
This annotation specifies where it is legal to use an annotation type. The possible values are constants from the
java.lang.annotation.ElementType enumeration which provides a simple classification of the declared elements in a Java program.
There are the following constants:

  • ANNOTATION_TYPE – Annotation type declaration – used for other annotations.
    @MyAnnotation
    public @interface AnotherAnnotation {..}
    
  • CONSTRUCTOR – Constructor declaration
    public class MyAnnotatedClass {
        @MyAnnotation
        public MyAnnotatedClass() {..}
    }
    
  • FIELD – Field declaration (includes enum constants)
    @MyAnnotation
    private String myAnnotatedField;
    
  • LOCAL_VARIABLE – Local variable declaration – It can’t be read at runtime, so it is used only for compile-time things, like the @SuppressWarnings annotation.
    public void someMethod() {
        @MyAnnotation int myLocalVariable = 0;
    }
    
  • METHOD – Method declaration
    @MyAnnotation
    public void myAnnotatedMethod() {..}
    
  • PACKAGE – Package declaration
    @MyAnnotation
    package com.ho.myannotatedpackage;
    
  • PARAMETER – Parameter declaration
    public void myMethod(@MyAnnotation param) {..}
    
  • TYPE – Class, interface (including annotation type), or enum declaration
    @MyAnnotation
    public class MyAnnotatedClass {..}
    

It is possible to specify multiple ElementTypes for a given annotation:

@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})

Example : validation with annotations

When an annotation is implemented, it is necessary to tell two things to the compiler:

  • where it can be applied (that’s the @Target annotation);
  • what its retention policy is (aptly named @RetentionPolicy; some annotations are kept only at the source level, others at runtime).

Below, the official description of annotations retention policy:

+ RetentionPolicy.SOURCE: Discard during the compile. These annotations don't make any sense after the compile has completed, so they aren't written to the bytecode.
 Example: @Override, @SuppressWarnings

+ RetentionPolicy.CLASS: Discard during class load. Useful when doing bytecode-level post-processing. Somewhat surprisingly, this is the default.

+RetentionPolicy.RUNTIME: Do not discard. The annotation should be available for reflection at runtime. Example: @Deprecated

In our example, we will use the annotations with the JDK 1.6 for validate the POJO values on two criterias ans set its name:

  • an annotation Mandatory to specify if a field is mandatory or not,
  • an annotation MaxLength to specify the max length of a String field,
  • an annotation FiledName to set the field’s name,

STEP 1
Create a new project named test_annot_valid with the following librairies in test_annot_valid\lib folder:

  • commons-beanutils-1.9.0.jar,
  • commons-lang-2.6.jar,
  • commons-logging-1.1.3.jar,

For information, our project structure will be:
test_annot_valid-scr1

STEP 2
We will create the definition of new annotations by creating 3 new classes in the package “com.ho.annotations.common.annotations”:

FieldName:

package com.ho.annotations.common.annotations;

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

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface FieldName {
	
	EnumFieldName name();
	
	public enum EnumFieldName{
		ID,
		NAME,
		PRICE,
		LAST_READ_DATETIME,
		NB_OF_READ,
		AUTHORS
		;
	}
}

Mandatory:

package com.ho.annotations.common.annotations;

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


/**
 * Marks the field as Mandatory : a null value is illegal.
 * @author huseyin
 *
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Mandatory {

}

Maxlength:

package com.ho.annotations.common.annotations;

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

/**
 * Specify the maximum length of the annotated field.
 * @author huseyin
 *
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface MaxLength {
	int maxLength();
}

STEP 3
Then, it is necessary to create POJO on whom the previous annotations must be applied. The class AbstractEntity will contain a list of errors corresponding to validation errors:

package com.ho.annotations.data.entity;

import java.util.ArrayList;
import java.util.List;

public abstract class AbstractEntity {

	// ------------------------- PROTECTED ATTRIBUTES
	protected List<ErrorEntity> errors = new ArrayList<ErrorEntity>();
	
	// ------------------------------- PUBLIC METHODS
	public void addError(ErrorEntity error){
		this.errors.add(error);
	}
	
	public void addErrors(List<ErrorEntity> errors){
		this.errors.addAll(errors);
	}
	
	public boolean hasError(){
		return errors!=null && errors.size()>0;
	}
	
	public List<ErrorEntity> getErrors(){
		List<ErrorEntity> result = new ArrayList<ErrorEntity>();
		for(ErrorEntity error : errors){
			result.add(error);
		}//end-for
		return result;
	}
}

And an error entity ErrorEntity class representing one error that occured when validating the object:

package com.ho.annotations.data.entity;

import com.ho.annotations.common.annotations.FieldName;

/**
 * Entity class representing one error that occured when validating the object
 * @author huseyin
 *
 */
public class ErrorEntity extends AbstractEntity{
	
	public static enum ErrorLevel{
		ERROR,
		WARNING,
		INFO,
		;
	}
	
	// --------------------------- PRIVATE ATTRIBUTES
	private Integer id;
	private FieldName.EnumFieldName enumFieldName;
	private String error;
	private Integer entityId;
	private ErrorLevel errorLevel;
	
	// ---------------------------------- CONSTRUCTOR
	public ErrorEntity(){}
	
	public ErrorEntity(FieldName.EnumFieldName enumFieldName, String error){
		this.enumFieldName = enumFieldName;
		this.error = error;
		this.errorLevel = ErrorLevel.ERROR;
	}
	
	public ErrorEntity(FieldName.EnumFieldName enumFieldName, String error, ErrorLevel errorLevel){
		this.enumFieldName = enumFieldName;
		this.error = error;
		this.errorLevel = errorLevel;
	}
	
	// ---------------------------- PUBLIC ATTRIBUTES
	@Override
	public String toString(){
		StringBuilder errorBuilder = new StringBuilder();
		errorBuilder.append(getEnumFieldName());
		errorBuilder.append(" ");
		errorBuilder.append(getError());
		return errorBuilder.toString();
	}
	
	
	// --------------------------------- GET/SET TERS
	public Integer getId() {
		return id;
	}

	public void setId(Integer id) {
		this.id = id;
	}

	public FieldName.EnumFieldName getEnumFieldName() {
		return enumFieldName;
	}

	public void setEnumFieldName(FieldName.EnumFieldName enumFieldName) {
		this.enumFieldName = enumFieldName;
	}

	//...etc.
	
	// ------------------------- PROTECTED ATTRIBUTES
}

Finally, the entity class BookEntity will be validated:

package com.ho.annotations.data.entity;

import java.util.Calendar;

import com.ho.annotations.common.annotations.FieldName;
import com.ho.annotations.common.annotations.Mandatory;
import com.ho.annotations.common.annotations.MaxLength;

/**
 * Entity mapping the table BOOK
 * @author huseyin
 *
 */
public class BookEntity extends AbstractEntity{

	// --------------------------- PRIVATE ATTRIBUTES
	// ID
	@Mandatory
	@FieldName(name=FieldName.EnumFieldName.ID)
	private Integer id;

	// NAME
	@Mandatory
	@MaxLength(maxLength = 10)
	@FieldName(name=FieldName.EnumFieldName.NAME)
	private String name;

	// PRICE
	@Mandatory
	@FieldName(name=FieldName.EnumFieldName.PRICE)
	private Double price;
	
	// NB_OF_READ
	@FieldName(name=FieldName.EnumFieldName.NB_OF_READ)
	private Integer numberOfRead;

	// LAST_READ_DATETIME
	@FieldName(name=FieldName.EnumFieldName.LAST_READ_DATETIME)
	private Calendar lastReadDateTime;

	// AUTHORS
	@FieldName(name=FieldName.EnumFieldName.AUTHORS)
	private String[] authors;
	
	// ---------------------------------- CONSTRUCTOR
	public BookEntity(){}
	
	// ------------------------------- PUBLIC METHODS
	@Override
	public String toString(){
		return this.getClass().getName()
				+ "[id="+id
				+ ", name="+name
				+ ", price="+price
				+ ", numberOfRead="+numberOfRead
				+ ", lastReadDateTime="+lastReadDateTime
				+ ", authors="+authors
				+ "]";
	}

	// --------------------------------- GET/SET TERS
	public Integer getId() {
		return id;
	}

	public void setId(Integer id) {
		this.id = id;
	}
	//...etc.
}

So, the book entity has the following fields:

  • id containing the technical ID : mandatory field named ID,
  • name corresponding to the book’s name : mandatory field named NAME with a max length of 10 characters,
  • price corresponding to the book’s price : mandatory field named PRICE,
  • numberOfRead corresponding to the number of read of this book : no mandatory field named NB_OF_READ,
  • lastReadDateTime corresponding to the last read datetime of this book : no mandatory field named LAST_READ_DATETIME,
  • authors containing the book’s authors : no mandatory field named AUTHORS,

STEP 4
In this step, we create an utility class ValidatorUtil to handle the validation of entities:

package com.ho.annotations.validator;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.beanutils.PropertyUtils;

import com.ho.annotations.common.annotations.FieldName;
import com.ho.annotations.common.annotations.Mandatory;
import com.ho.annotations.common.annotations.MaxLength;
import com.ho.annotations.data.entity.AbstractEntity;
import com.ho.annotations.data.entity.ErrorEntity;

/**
 * This class handles the validation of the entity.
 * @author huseyin
 */
public final class ValidatorUtil {

	// ---------------------------------- CONSTRUCTOR
	private ValidatorUtil(){}

	// ------------------------------- PUBLIC METHODS
	/**
	 * Validation of the given entity annotations
	 * @param myEntity
	 * @throws Exception
	 */
	public static void validateEntity(AbstractEntity myEntity) throws Exception{
		myEntity.addErrors(validateAnnotatedFields(myEntity));
	}

	
	// ------------------------------ PRIVATE METHODS
	/**
	 * Validates all the annotated field of the given entities.
	 * Supported annotation are {@see Mandatory} {@see MaxLength}
	 * @param myEntity
	 * @return
	 * @throws Exception
	 */
	private static List<ErrorEntity> validateAnnotatedFields(AbstractEntity myEntity) throws Exception{
		List<ErrorEntity> errors = new ArrayList<ErrorEntity>();
		
		for(Field field : myEntity.getClass().getDeclaredFields()){
			// Mandatory field
			final Mandatory mandatory = field.getAnnotation(Mandatory.class);
			if(mandatory != null){
				final Object fieldValue = PropertyUtils.getProperty(myEntity, field.getName());
				if(fieldValue == null){
					final FieldName fieldName = field.getAnnotation(FieldName.class);
					errors.add(new ErrorEntity(fieldName.name(), "Mandatory."));
				}
			}// Mandatory
			
			// MaxLength
			final MaxLength maxLength = field.getAnnotation(MaxLength.class);
			if(maxLength != null){
				final Object fieldValue = PropertyUtils.getProperty(myEntity, field.getName());
				if(fieldValue instanceof String ){
					if(maxLength.maxLength() < ((String)fieldValue).length()){
						final FieldName fieldName = field.getAnnotation(FieldName.class);
						errors.add(new ErrorEntity(fieldName.name(), "Value is too large. Max length is: "+maxLength.maxLength()));
					}
				}
			}// MaxLength
					
		}//end-for
		
		return errors;
	}
}

The important point of this class is the part concerning the checking of annotations on entity object:

// Mandatory field
final Mandatory mandatory = field.getAnnotation(Mandatory.class);
if(mandatory != null){
	final Object fieldValue = PropertyUtils.getProperty(myEntity, field.getName());
	if(fieldValue == null){
		final FieldName fieldName = field.getAnnotation(FieldName.class);
		errors.add(new ErrorEntity(fieldName.name(), "Mandatory."));
	}
}// Mandatory
// MaxLength
final MaxLength maxLength = field.getAnnotation(MaxLength.class);
if(maxLength != null){
	final Object fieldValue = PropertyUtils.getProperty(myEntity, field.getName());
	if(fieldValue instanceof String ){
		if(maxLength.maxLength() < ((String)fieldValue).length()){
			final FieldName fieldName = field.getAnnotation(FieldName.class);
			errors.add(new ErrorEntity(fieldName.name(), "Value is too large. Max length is: "+maxLength.maxLength()));
		}
	}
}// MaxLength

STEP 5
Finally, we are creating a main method TestValidator to check our valiation based on annotations:

package com.ho.annotations.validator.test;

import java.util.GregorianCalendar;
import java.util.Iterator;

import com.ho.annotations.data.entity.AbstractEntity;
import com.ho.annotations.data.entity.BookEntity;
import com.ho.annotations.data.entity.ErrorEntity;
import com.ho.annotations.validator.ValidatorUtil;

public class TestValidator {

	public static void main(String[] args) {
		try{
			// Test n°1 : All fields are filled => OK
			{
				System.out.println("----- Test n°1 : All fields are filled ----"); 
				final BookEntity book = new BookEntity();
				book.setId(456789); // Mandatory
				book.setName("Germinal"); // Mandatory
				book.setPrice(123.23); // Mandatory
				book.setNumberOfRead(12);
				book.setLastReadDateTime(GregorianCalendar.getInstance());
				book.setAuthors(new String[]{"Emile Zola"});
				ValidatorUtil.validateEntity(book);
				printErrors(book);
			}
			
			// Test n°2 : Some optional fields are missing => OK
			{
				System.out.println("----- Test n°2 : Some optional fields are missing ----"); 
				final BookEntity book = new BookEntity();
				book.setId(456789); // Mandatory
				book.setName("Germinal"); // Mandatory
				book.setPrice(123.23); // Mandatory
				book.setNumberOfRead(12);
				book.setLastReadDateTime(GregorianCalendar.getInstance());
				ValidatorUtil.validateEntity(book);
				printErrors(book);
			}

			// Test n°3 : Some mandatory fields are missing => NOK Errors messages
			{
				System.out.println("----- Test n°3 : Some mandatory fields are missing ----"); 
				final BookEntity book = new BookEntity();
				book.setId(456789); // Mandatory
				book.setName(null); // Mandatory
				book.setPrice(123.23); // Mandatory
				book.setNumberOfRead(12);
				book.setLastReadDateTime(GregorianCalendar.getInstance());
				book.setAuthors(new String[]{"Emile Zola"});
				ValidatorUtil.validateEntity(book);
				printErrors(book);
			}
			
			// Test n°4 : All fields are filled but a field's value is too large (maxlength is reached) => NOK Errors messages
			{
				System.out.println("----- Test n°4 : All fields are filled but a field's value is too large (maxlength is reached) ----"); 
				final BookEntity book = new BookEntity();
				book.setId(456789); // Mandatory
				book.setName("dsf sdf ezrezk jlezjr ezrezlrjezlrjezlrjez lrjezlkjr ezrezjrezrezrez"); // Mandatory
				book.setPrice(123.23); // Mandatory
				book.setNumberOfRead(12);
				book.setLastReadDateTime(GregorianCalendar.getInstance());
				book.setAuthors(new String[]{"Emile Zola"});
				ValidatorUtil.validateEntity(book);
				printErrors(book);
			}			
		}catch(Throwable th){
			th.printStackTrace();
		}
	}
	

	private static void printErrors(AbstractEntity myEntity){
		if(myEntity!=null && myEntity.hasError()){
			for (Iterator<ErrorEntity> iterator = myEntity.getErrors().iterator(); iterator.hasNext();) {
				ErrorEntity currErrorEntity = iterator.next();
				System.out.println(currErrorEntity);
			}//end-for
		}else{
			System.out.println("No error found in the validation of this entity!");
		}//end-if
	}	
}

The outputs are:

----- Test n°1 : All fields are filled ----
No error found in the validation of this entity!
----- Test n°2 : Some optional fields are missing ----
No error found in the validation of this entity!
----- Test n°3 : Some mandatory fields are missing ----
NAME Mandatory.
----- Test n°4 : All fields are filled but a field's value is too large (maxlength is reached) ----
NAME Value is too large. Max length is: 10

That’s all!!!!

Huseyin OZVEREN