Creating custom annotation in Java for field validation
1. Introduction
- Java annotation are a form of metadata that can be added to the code which provides information to the compiler or runtime environment.
- This information can be used for various use cases such as providing hint to the compiler, adding code generation or providing information to frameworks etc.
- In this article learn about annotation and will create annotation for field validation in class.
2. Annotation Explained!
- Java Annotations are kind of markers that can be added to classes, methods , fields etc.
- By adding annotation to the Java classes, methods or fields , it provides metadata information to the compiler or runtime environment.
- Annotation start with “@” symbol and can be built in or custom.
- For example, `@Component`, `@Repository` are example of built in annotation in Spring framework.
Annotation Interface
- First we need to define our annotation `CustomAnnotation` as interface.
- We need to mark this annotation with @Retention and @Target .
- @Retention (RetentionPolicy.RUNTIME) specifies that annotation will be retained at runtine.
- @Target ({ ElementType.TYPE, ElementType.METHOD }) restricts annotation to be applicable on `Class` and `Method`.
@Retention(RetentionPolicy.RUNTIME) // when annotation will be available
@Target({ ElementType.TYPE, ElementType.METHOD }) // where annotation can be applied
public @interface CustomAnnotation {
String value() default "";
int count() default 0;
}
Marking Annotation
- Now lets mark target class and method with the annotation. We need to pass the metadata info which value and count that we defined in annotation.
@CustomAnnotation(value = "a_class", count = 2)
public class MyClass {
@CustomAnnotation(value = "a_method", count = 1)
public static void method(){
}
}
Reading metadata
- Now that we have annotation defined and used, lets read them at runtime.
- We can get class level metadata using `Class.getAnnotation()` and method level metadata with `Method.getAnnotation()`.
- Once we have annotation instance we can access its property value by simple method call.
- In order to access value of class , we can just call `annotation.value()`.
private static void example1() throws NoSuchMethodException {
Class<MyClass> myClass = MyClass.class;
CustomAnnotation annotation = myClass.getAnnotation(CustomAnnotation.class);
String value = annotation.value();
int count = annotation.count();
System.out.println("value: "+value+", count: "+count);
Method method = myClass.getMethod("method");
CustomAnnotation annotation1 = method.getAnnotation(CustomAnnotation.class);
System.out.println("value: "+annotation1.value());
System.out.println("count: "+annotation1.count());
}
Output
- As we can see below we got class & method metadata value .
value: a_class, count: 2
value: a_method
count: 1
Now lets create our own annotation to validate fields in Java class.
3. Creating Custom Field Validator Annotation
- Now lets extend our basic example and create something useful.
- Lets create two annotation that will help us validating field in java class.
- We will create @NotNull and @Length annotation.
- @NotNull: it will check if the annotated field is non null . if its null then throws exception
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface NotNull {
String message() default "Field cannot be null";
}
- @Length: it will check if the annoted field respects min or max length passed to this annotation.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Length {
int max() default Integer.MAX_VALUE;
int min() default Integer.MIN_VALUE;
}
- Lets create `Customer` POJO / record and annotate filed with appropriate annotation.
- For password we are making sure its not null and min size 28 and max 100.
- For username and emailId we are checking if its not null.
public record Customer(
@NotNull(message = "username must not be null")
String username,
@NotNull(message = "password must not be null")
@Length(min = 28, max = 100)
String password,
@NotNull(message = "emailId must not be null")
String emailId
) { }
- Now lets write validation logic that will read above annotation and metadata at runtime, then perform validation and throw exception with the provided message if required.
- At first lets get all the fields from customer record.
- Now lets iterate over each field and get all the annotation for that field.
- For each annotation we get value and handle it based on annotation type.
public static void validate(Object object) throws IllegalAccessException {
List<Class<? extends Annotation>> annotations = List.of(NotNull.class, Length.class);
Class<?> clazz = object.getClass();
Field[] declaredFields = clazz.getDeclaredFields();
System.out.println(Arrays.toString(declaredFields));
for(Field field: declaredFields) {
List<? extends Class<? extends Annotation>> targetAnnotations = getTargetAnnotations(annotations, field);
targetAnnotations.forEach(
a -> {
field.setAccessible(true);
String typeName = a.getSimpleName();
Object fieldVal = getObject(object, field);
handle(field, typeName, fieldVal);
}
);
}
}
- Here is the logic to get object value.
private static Object getObject(Object object, Field field) {
Object fieldVal;
try {
fieldVal = field.get(object);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
return fieldVal;
}
- Here is the logic to get all the annoation type for the field.
private static List<? extends Class<? extends Annotation>> getTargetAnnotations(List<Class<? extends Annotation>> annotations, Field field) {
return Arrays.stream(field.getAnnotations())
.map(Annotation::annotationType)
.filter(annotations::contains)
.toList();
}
- Handle logic takes the typeName of the annotation and operate on it.
- For NotNull we check if the fieldVal null , if it is then we extract the message and throw `IllegalArgumentException`.
- For Length, we are extracting min and max length , then we are checking if the fieldVal length lies in that range or not.
- If not then we throw `IllegalArgumentException` with custom error message.
private static void handle(Field field, String typeName, Object fieldVal) {
switch (typeName) {
case "NotNull" -> {
if (fieldVal != null)
return;
String message = field.getAnnotation(NotNull.class)
.message();
throw new IllegalArgumentException(message);
}
case "Length" -> {
Length lengthAnnotation = field.getAnnotation(Length.class);
int max = lengthAnnotation.max();
int min = lengthAnnotation.min();
if (fieldVal.toString().length() >= min && fieldVal.toString().length() <= max)
return;
String message = "length should be in the range of min %d and max %d".formatted(min, max);
throw new IllegalArgumentException(message);
}
}
}
Output
- it throws password length not matching error.
Exception in thread "main" java.lang.IllegalArgumentException: length should be in the range of min 28 and max 100
at org.example.Annotation.FieldValidator.handle(FieldValidator.java:83)
at org.example.Annotation.FieldValidator.lambda$validate$0(FieldValidator.java:42)
at java.base/java.lang.Iterable.forEach(Iterable.java:75)
at org.example.Annotation.FieldValidator.validate(FieldValidator.java:37)
at org.example.Annotation.Main.example3(Main.java:16)
at org.example.Annotation.Main.main(Main.java:10)
4. Conclusion
- Annotation are used extensively in Java developement and certainly its all over used inside Framework like Spring.
- In this article we learnt about Annotation in Java. We also build our own field validator annotation.
Before You Leave
- Upgrade your Java skills with Grokking the Java Interview.
- If you want to upskill your Java skills, you should definitely check out
[NEW] Master Spring Boot 3 & Spring Framework 6 with Java
[ 38 hrs content, 4.7/5 stars, 6k+ students already enrolled] - Find More Java/Spring Blogs
- Subscribe to Java/Spring Newsletter