I'm facing an issue where both the @NotBlank and @Size(min) validations are triggered simultaneously on empty fields in Spring Boot, but I expect the @NotBlank validation to run first.
Here’s the scenario:
I have a field annotated with @NotBlank (to ensure it's not empty) and @Size(min = 2) (to ensure it's at least 2 characters). When I send an empty string in the request body, the validation triggers both errors: "Name is required" from @NotBlank "Name must be between 2 and 50 characters" from @Size(min = 2) However, I expected the validation to fail on @NotBlank first and not evaluate the @Size constraint when the field is empty.
Here’s the relevant part of my code:
@NotBlank(message = "Name is required")
@Size(min = 2, max = 50, message = "Name must be between 2 and 50 characters")
private String name;
Why is @Size(min = 2) being triggered even when the field is empty? How can I ensure that @NotBlank is evaluated first?
I’m using Spring Boot 3 with Jakarta Validation.
Any help would be much appreciated.
I have resolved this issue by creating a custom validator to enforce the validation order. Below is the solution I implemented:
Custom Validator for @RequiredSize
RequiredSize Annotation
package com.example.app.model.validation;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = RequiredSizeValidator.class)
public @interface RequiredSize {
String message() default "Invalid size";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
int min() default 0;
int max() default Integer.MAX_VALUE;
String minMessage() default "Field must have at least {min} characters";
String maxMessage() default "Field must have no more than {max} characters";
String requiredMessage() default "This field is required";
}
RequiredSizeValidator
package com.example.app.model.validation;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
public class RequiredSizeValidator implements ConstraintValidator<RequiredSize, String> {
private int min;
private int max;
private String minMessage;
private String maxMessage;
private String requiredMessage;
@Override
public void initialize(RequiredSize constraintAnnotation) {
min = constraintAnnotation.min();
max = constraintAnnotation.max();
minMessage = constraintAnnotation.minMessage();
maxMessage = constraintAnnotation.maxMessage();
requiredMessage = constraintAnnotation.requiredMessage();
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null || value.trim().isEmpty()) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(requiredMessage)
.addConstraintViolation();
return false;
}
boolean valid = true;
if (value.length() < min) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(minMessage)
.addConstraintViolation();
valid = false;
}
if (value.length() > max) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(maxMessage)
.addConstraintViolation();
valid = false;
}
return valid;
}
}
Applying the Annotation
package com.example.app.model;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import jakarta.persistence.Table;
import java.time.LocalDateTime;
import com.example.app.model.validation.RequiredSize;
@Entity
@Table(name = "contact_requests")
public class ContactRequest {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@RequiredSize(min = 2, max = 50, requiredMessage = "Name is required", minMessage = "Name must be at least 2 characters", maxMessage = "Name must be no more than 50 characters")
private String name;
@NotBlank(message = "Email is required")
@Email(message = "Email should be valid")
private String email;
@NotBlank(message = "Message is required")
@Size(max = 500, message = "Message cannot exceed 500 characters")
private String message;
private LocalDateTime submittedAt;
// Getters and Setters
}