Bean Validation in Spring Boot is a framework for validating the properties of a JavaBean using annotations. It leverages the Java Bean Validation API (JSR 380) and Hibernate Validator to perform validation. It
Ensure data integrity by validating user input at the application layer.
Reduce boilerplate validation logic in your code by relying on declarative annotations.
Integrates seamlessly with Spring Boot's controller and service layers.
@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);
}
public class Order {
@Valid
@NotNull
private User user;
}
// first create groups
public interface CreateGroup {}
public interface UpdateGroup {}
// attach group with groups attrubute
public class User {
@NotNull(groups = CreateGroup.class)
private String name;
@NotNull(groups = UpdateGroup.class)
private Integer id;
}
// use @validated with specific group that want to validate
@PostMapping
public ResponseEntity<String> createUser(@Validated(CreateGroup.class) @RequestBody User user) {
return ResponseEntity.ok("User created");
}
public class BeanValidator {
private static final ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
private static final Validator validator = factory.getValidator();
public static <T> void validate(T bean) {
Set<ConstraintViolation<T>> violations = validator.validate(bean);
if (!violations.isEmpty()) {
StringBuilder sb = new StringBuilder();
for (ConstraintViolation<T> violation : violations) {
sb.append(violation.getPropertyPath()).append(": ").append(violation.getMessage()).append("\n");
}
throw new IllegalArgumentException("Bean validation failed:\n" + sb.toString());
}
}
}
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import org.springframework.web.multipart.MultipartFile;
import java.util.Arrays;
import java.util.List;
/**
* Validator class for @ValidFile annotation, used to validate file size and extension
* @see ValidFile
* @Author saurabh vaish
* @Date 04-09-2024
*/
public class FileValidator implements ConstraintValidator<ValidFile, Object> {
private long maxSizeBytes;
private List<String> allowedExtensions;
@Override
public void initialize(ValidFile constraintAnnotation) {
this.maxSizeBytes = constraintAnnotation.maxSizeInMb() * 1024 * 1024;
this.allowedExtensions = Arrays.asList(constraintAnnotation.allowedExtensions());
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
return switch (value) {
case null -> true; // null values are validated with @NotNull
case MultipartFile multipartFile -> validateFile(multipartFile, context);
case MultipartFile[] multipartFiles -> validateFiles(List.of(multipartFiles), context);
case List<?> list -> validateFiles((List<MultipartFile>) list, context);
default -> false;
};
}
private boolean validateFile(MultipartFile file, ConstraintValidatorContext context) {
if (file.isEmpty()) {
return true; // Empty files are allowed, use @NotNull if you want to enforce file presence
}
String fileName = file.getOriginalFilename();
if (fileName == null || fileName.contains("..")) {
addConstraintViolation(context, "Invalid file name");
return false;
}
String extension = getFileExtension(fileName);
if (!allowedExtensions.contains(extension.toLowerCase())) {
addConstraintViolation(context, "Invalid file type. Allowed types are: " + String.join(", ", allowedExtensions));
return false;
}
if (file.getSize() > maxSizeBytes) {
addConstraintViolation(context, "File size exceeds the maximum allowed size of " + (maxSizeBytes / 1024 / 1024) + "MB");
return false;
}
return true;
}
private boolean validateFiles(List<MultipartFile> files, ConstraintValidatorContext context) {
for (MultipartFile file : files) {
if (!validateFile(file, context)) {
return false;
}
}
return true;
}
private void addConstraintViolation(ConstraintValidatorContext context, String message) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(message).addConstraintViolation();
}
private String getFileExtension(String fileName) {
int dotIndex = fileName.lastIndexOf('.');
return (dotIndex == -1) ? "" : fileName.substring(dotIndex + 1);
}
}
@NotNull(message = "Image file is required if imageUrl is not provided")
@Schema(description = "Image file (optional if imageUrl is provided)", type = "string", format = "binary")
@ValidFile(message = "Invalid file. Only JPG, JPEG, and PNG files up to 5MB are allowed.", maxSizeInMb = 5,allowedExtensions = {"jpg", "jpeg", "png"})
MultipartFile file
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ProblemDetail handleValidationExceptions(MethodArgumentNotValidException ex) {
ProblemDetail problemDetail = ProblemDetail.forStatus(HttpStatus.BAD_REQUEST);
problemDetail.setTitle("Validation Error");
problemDetail.setDetail("Invalid input provided");
ex.getBindingResult().getFieldErrors().forEach(error ->
problemDetail.setProperty(error.getField(), error.getDefaultMessage())
);
return problemDetail;
}
}
// reponse
{
"type": "about:blank",
"title": "Validation Error",
"status": 400,
"detail": "Invalid input provided",
"instance": "/api/users",
"name": "Name must not be blank",
"email": "Email must be a valid address"
}