When developing web applications or REST APIs with Spring Boot, validation of request data is unavoidable. For example, during user registration, you need to check whether the name or email address is empty, and whether the format is correct.
This is where the @Valid annotation comes in handy. In this article, we will explain everything from the basic role of @Valid to how to define actual validation rules in a practical manner.
What is @Valid?
@Valid is an annotation that triggers validation processing compliant with the Jakarta Bean Validation (formerly JSR 303/380) specification in Java. In Spring Boot, by introducing spring-boot-starter-validation, you can easily perform validation on controller arguments and Java objects.
However, @Valid itself only has a trigger-like role that says “make this object the target of validation.” What is validated and how is defined by the constraint annotations attached to the fields.
Why is it frequently used in API development?
In Spring Boot REST APIs, it is common to receive request data from clients in formats such as JSON. At this time, by mapping the received data to a POJO (Plain Old Java Object) and attaching @Valid, Spring automatically executes validation.
@PostMapping("/users")
public ResponseEntity<String> createUser(@RequestBody @Valid UserRequest userRequest) {
return ResponseEntity.ok("User created");
}
This allows you to design a system where, if the request is invalid, Spring throws a MethodArgumentNotValidException and returns an appropriate error response.
Validation rules are defined by annotations
For objects where validation processing is enabled by @Valid, you can finely control the validation content by attaching constraint annotations to each field.
public class UserRequest {
@NotBlank(message = "名前は必須です")
@Size(min = 2, max = 20, message = "名前は2〜20文字で入力してください")
private String name;
@Email(message = "メールアドレスの形式が正しくありません")
private String email;
}
In this way, you can also combine multiple constraints per field.
Commonly used constraint annotations
The following are some of the constraint annotations that can be used for validation.
| Annotation | Description |
|---|---|
@NotNull | Must not be null |
@NotBlank | Empty strings or whitespace-only strings are prohibited |
@NotEmpty | Empty collections, arrays, or strings are prohibited |
@Size(min=, max=) | Constraint on length or number of elements |
@Email | Whether it is in email format |
@Pattern(regexp=) | Whether it matches a regular expression |
@Min, @Max | Constraints on numeric range |
@Positive, @Negative | Whether it is positive or negative |
@Past, @Future | Whether the date is in the past or future |
By specifying the message attribute on these annotations, you can also define custom error messages.
Validation of nested objects
@Valid can be applied recursively to nested objects as well.
public class OrderRequest {
@Valid
private Address address;
}
In this case, the validation rules defined within the Address class are also applied.
Manual validation in the service layer
In classes other than controllers (such as the Service layer), you can also explicitly execute validation using Validator.
@Service
public class UserService {
private final Validator validator;
public UserService(Validator validator) {
this.validator = validator;
}
public void register(UserRequest request) {
Set<ConstraintViolation<UserRequest>> violations = validator.validate(request);
if (!violations.isEmpty()) {
throw new IllegalArgumentException("Validation failed: " + violations);
}
// Registration processing
}
}
Differences between @Validated and how to use them properly
Comparison table of @Valid and @Validated
The two are similar, but they differ in where they can be used and in their functionality. Let’s organize this as a quick reference when you’re unsure in practice.
| Aspect | @Valid (Jakarta) | @Validated (Spring) |
|---|---|---|
| Origin | Jakarta Bean Validation specification | Spring Framework proprietary |
| Main use | Recursive validation of controller arguments and nested DTOs | Group validation, method argument validation |
| Group specification | Not possible | Possible (@Validated(OnCreate.class), etc.) |
| Exception type | MethodArgumentNotValidException | ConstraintViolationException |
| Attachment to class | Not possible (fields/arguments only) | Possible (enables validation of the entire Bean) |
| Nested validation | Recursive application to child DTO fields is intuitive | When nesting, it is common to use @Valid together |
To put it briefly, using @Valid for standard validation of @RequestBody in controllers, and using @Validated when you want to validate method arguments in the Service layer or switch rules between registration/update is the practical approach that leads to the fewest accidents.
Spring’s proprietary @Validated annotation can also be used for purposes similar to @Valid. The main difference is that it allows more flexible control, such as group validation and per-method validation.
@Component
@Validated
public class UserValidator {
public void validate(@Valid UserRequest request) {
// Automatically validated
}
}
DTO design rules in practice
Considering operations, aligning DTO validation with the following rules makes maintenance easier.
- Separate API input-only DTOs from DB entities
- Limit the responsibilities per field and avoid excessive validation
- Design messages with awareness of whether they are for users or for logs
- Use
@NotNulland@NotBlankproperly according to purpose (prefer@NotBlankfor strings)
Rather than “attaching constraints to everything just in case,” defining only what is necessary as an input contract makes the code more resilient to changes.
Standardizing error responses
When using @Valid, it is important to first standardize the return format of MethodArgumentNotValidException.
If this varies every time, the implementation cost on the frontend side increases.
@RestControllerAdvice
public class ApiExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, Object>> handleValidation(MethodArgumentNotValidException ex) {
var errors = ex.getBindingResult().getFieldErrors().stream()
.map(error -> Map.of(
"field", error.getField(),
"message", error.getDefaultMessage()
))
.toList();
return ResponseEntity.badRequest().body(Map.of(
"code", "VALIDATION_ERROR",
"errors", errors
));
}
}
By fixing the response keys (code, errors, field, message) in this way, handling on the consumer side becomes stable.
Common pitfalls
Validation passes but business requirements are not met
Annotation constraints are strong for “format checks,” but verification of “business rules” is a separate matter.
For example, “whether the same email address already exists” requires a DB query, so add additional checks in the Service layer.
Forgetting to attach @Valid to nested DTOs
Even if you attach @Valid only to the parent DTO, recursive validation will not occur if @Valid is not attached to the child DTO field.
When using nested structures, check the annotations on both parent and child.
Hardcoding validation messages too much
If there is a possibility of supporting multiple languages in the future, a design that consolidates messages into messages.properties is effective.
@NotBlank(message = "{validation.name.required}")
private String name;
Summary
By using the @Valid annotation, you can implement input value validation in Spring Boot concisely and flexibly. The actual validation rules are defined by attaching constraint annotations to the target fields. This enhances the robustness of the API while also providing easy-to-understand error messages to users.
In addition to APIs, validation can also be performed within any component using Validator, so you can use it differently depending on business logic.
Please make use of the @Valid annotation to create more robust applications!
Frequently Asked Questions (FAQ)
Which should come first, @Valid or @RequestBody?
The behavior is the same regardless of which comes first. Spring MVC does not depend on annotation order. For readability, it is good practice to standardize on one within your team.
Why doesn’t validation run even when @Valid is attached?
There are three common causes:
spring-boot-starter-validationis not included in the dependencies (note that it is no longer automatically added since Spring Boot 2.3).- You forgot to attach
@Validto the field of a nested DTO. - You expect method validation in the Service layer, but the class does not have
@Validatedattached (@Validalone does not trigger method validation).
What is the difference between MethodArgumentNotValidException and ConstraintViolationException?
The former is thrown when validation fails with @RequestBody @Valid, and the latter is thrown when it fails on method arguments (path parameters or query parameters) of a Bean with @Validated. By preparing separate handlers in @RestControllerAdvice to catch both, you can unify the error response format.
I want to localize validation messages
Define keys in the format validation.name.required=名前は必須です in messages.properties / messages_en.properties, etc., and reference them in constraint annotations using curly braces like @NotBlank(message = "{validation.name.required}"). Spring Boot automatically switches according to the language resolved by LocaleResolver.
How do I use @NotNull, @NotBlank, and @NotEmpty properly?
@NotNull: OK as long as it is not null (empty string""passes).@NotEmpty: OK as long as it is not null or empty (length 0) (whitespace" "passes).@NotBlank: Prohibits null, empty, and whitespace-only strings. For string fields, it is basically safest to use@NotBlank.