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.

AnnotationDescription
@NotNullMust not be null
@NotBlankEmpty strings or whitespace-only strings are prohibited
@NotEmptyEmpty collections, arrays, or strings are prohibited
@Size(min=, max=)Constraint on length or number of elements
@EmailWhether it is in email format
@Pattern(regexp=)Whether it matches a regular expression
@Min, @MaxConstraints on numeric range
@Positive, @NegativeWhether it is positive or negative
@Past, @FutureWhether 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)
OriginJakarta Bean Validation specificationSpring Framework proprietary
Main useRecursive validation of controller arguments and nested DTOsGroup validation, method argument validation
Group specificationNot possiblePossible (@Validated(OnCreate.class), etc.)
Exception typeMethodArgumentNotValidExceptionConstraintViolationException
Attachment to classNot possible (fields/arguments only)Possible (enables validation of the entire Bean)
Nested validationRecursive application to child DTO fields is intuitiveWhen 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 @NotNull and @NotBlank properly according to purpose (prefer @NotBlank for 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:

  1. spring-boot-starter-validation is not included in the dependencies (note that it is no longer automatically added since Spring Boot 2.3).
  2. You forgot to attach @Valid to the field of a nested DTO.
  3. You expect method validation in the Service layer, but the class does not have @Validated attached (@Valid alone 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.