Custom Annotations and Validation in Spring Boot

Introduction

Validating user input is a critical part of any application to ensure data integrity and security. Spring Boot provides a robust mechanism for validation using annotations from the Javax.validation package. While standard annotations like @NotNull, @Size, and @Email cover many common validation scenarios, there are times when you need custom validation logic. In such cases, creating custom annotations and validators can be very effective.

This article will guide you through creating custom annotations and validators in a Spring Boot application with a practical example.

Creating Custom Annotations

Step 1. Create a Custom Annotation.

First, let's create a custom annotation @ValidAge to validate that a person's age falls within a specified range.

ValidAge.java

package com.example.demo.validation;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Constraint(validatedBy = AgeValidator.class)
@Target({ ElementType.FIELD, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidAge {
    String message() default "Invalid age: Age must be between 18 and 60";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
    int min() default 18;
    int max() default 60;
}

Step 2. Create the Validator Class.

Next, we need to implement the ConstraintValidator interface to define the validation logic.

AgeValidator.java

package com.example.demo.validation;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class AgeValidator implements ConstraintValidator<ValidAge, Integer> {
    private int min;
    private int max;
    @Override
    public void initialize(ValidAge validAge) {
        this.min = validAge.min();
        this.max = validAge.max();
    }
    @Override
    public boolean isValid(Integer age, ConstraintValidatorContext context) {
        if (age == null) {
            return false;
        }
        return age >= min && age <= max;
    }
}

Using Custom Annotations in a Spring Boot Application

Step 3. Create an Entity Class.

We'll create a User entity class that uses our custom @ValidAge annotation to validate the age field.

User.java

package com.example.demo.entity;
import com.example.demo.validation.ValidAge;
public class User {
    private Long id;
    private String name;
    @ValidAge
    private Integer age;
    // Getters and Setters
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
}

Step 4. Create a Controller.

Create a REST controller to handle incoming requests and demonstrate validation.

UserController.java

package com.example.demo.controller;
import com.example.demo.entity.User;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
@RestController
@Validated
public class UserController {
    @PostMapping("/users")
    public ResponseEntity<String> createUser(@Valid @RequestBody User user) {
        return new ResponseEntity<>("User is valid", HttpStatus.OK);
    }
}

Handling Validation Errors

By default, Spring Boot will return a 400 Bad Request response if validation fails. However, you can customize the error response.

Step 5. Customize Error Handling.

Create a global exception handler to customize the error response for validation errors.

GlobalExceptionHandler.java

package com.example.demo.exception;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.HashMap;
import java.util.Map;
@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<Map<String, String>> handleValidationExceptions(MethodArgumentNotValidException ex) {
        Map<String, String> errors = new HashMap<>();
        ex.getBindingResult().getFieldErrors().forEach(error ->
                errors.put(error.getField(), error.getDefaultMessage()));
        return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST);
    }
}

Running the Application

Run your Spring Boot application and test the validation by sending a POST request to /users with JSON data. Here are some sample requests and expected responses:

Valid Request

Request

POST /users
Content-Type: application/json
{
    "id": 1,
    "name": "John Doe",
    "age": 25
}

Response

HTTP/1.1 200 OK
User is valid

Invalid Request

Request

POST /users
Content-Type: application/json
{
    "id": 1,
    "name": "John Doe",
    "age": 15
}

Response

HTTP/1.1 400 Bad Request
Content-Type: application/json
{
    "age": "Invalid age: Age must be between 18 and 60"
}

Conclusion

Custom annotations and validation in Spring Boot provide a powerful mechanism to enforce business rules and ensure data integrity. By creating custom annotations and implementing your own validation logic, you can handle complex validation scenarios with ease. This guide has shown how to create and use custom annotations, implement a validator, and customize error handling in a Spring Boot application.


Similar Articles