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.