LearnAI ToolsCareerPractice BuildsPlayContact
Lesson 2419 min read

Spring MVC Validation

Learn how to validate bound form data with @Valid and BindingResult, and how to display validation errors back to the user.

Introduction

A form-backing object gets populated whether the data is sensible or not - an empty name, a negative age, or a malformed email will all bind just fine. Spring MVC integrates with the Bean Validation standard (JSR 380) so you can declare validation rules directly on your form object and have Spring check them automatically. This lesson covers @Valid, BindingResult, and how to show validation errors back to the user.

What You Will Learn
  • Why server-side validation is required even with client-side checks.
  • Common Bean Validation annotations like @NotBlank, @Size, and @Min.
  • How @Valid triggers validation on a bound object.
  • How BindingResult captures validation errors without throwing an exception.
  • How to display field-level errors in a Thymeleaf view.

Why Validate on the Server

Client-side validation (HTML required attributes, JavaScript checks) improves user experience but can always be bypassed - a user can disable JavaScript, or a request can be sent directly to your endpoint with a tool like curl. Server-side validation is the only validation you can actually rely on to protect your application's data integrity.

Bean Validation Annotations

The jakarta.validation.constraints package (included via the spring-boot-starter-validation dependency) provides annotations you place directly on the fields of your form object to declare validation rules.

AnnotationMeaning
@NotBlankString must not be null and must contain at least one non-whitespace character
@NotNullValue must not be null (works on any type)
@Size(min=, max=)String, collection, or array length must fall within the given range
@Min / @MaxNumeric value must be at least / at most the given value
@EmailString must be formatted like a valid email address
public class UserForm {
@NotBlank(message = "Name is required")
private String name;
@NotBlank(message = "Email is required")
@Email(message = "Email must be a valid address")
private String email;
@Min(value = 18, message = "Age must be at least 18")
private int age;
// getters and setters
}

Triggering Validation with @Valid

Adding @Valid in front of a @ModelAttribute parameter tells Spring to run Bean Validation on the bound object before the method body executes.

@PostMapping("/create")
public String submitForm(@Valid @ModelAttribute UserForm userForm, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return "user-form"; // re-show the form with error messages
}
userService.register(userForm);
return "redirect:/users";
}
BindingResult Must Come Right After

BindingResult must be declared as the parameter immediately following the @Valid object. If anything else comes between them, Spring throws validation errors as an exception instead of letting you handle them gracefully.

Reading Errors with BindingResult

BindingResult holds the outcome of both data binding and validation for the preceding object. hasErrors() tells you whether anything went wrong, and getFieldErrors() lets you inspect exactly which fields failed and why.

if (bindingResult.hasErrors()) {
for (FieldError error : bindingResult.getFieldErrors()) {
System.out.println(error.getField() + ": " + error.getDefaultMessage());
}
return "user-form";
}
Example Console Output

Click Run to see what this code prints.

Displaying Errors in the View

Because BindingResult is automatically exposed to the view alongside the form object, template engines can render per-field errors without any extra wiring. In Thymeleaf, th:if="${#fields.hasErrors('name')}" and th:errors="*{name}" do exactly this.

<input type="text" th:field="*{name}" />
<span th:if="${#fields.hasErrors('name')}" th:errors="*{name}" class="error"></span>

Common Mistakes

Avoid These Mistakes
  • Forgetting @Valid entirely, so the annotations on the form object are never actually checked.
  • Putting a parameter between the @Valid object and BindingResult, causing Spring to throw instead of populate it.
  • Relying only on client-side validation and skipping @Valid on the server.
  • Not returning the form view again when bindingResult.hasErrors() is true, which silently proceeds with invalid data.
  • Forgetting to add spring-boot-starter-validation as a dependency, so the validation annotations have no effect.

Best Practices

  • Always check bindingResult.hasErrors() immediately and return the form view if validation failed.
  • Give every validation annotation a clear, user-facing message.
  • Validate both format (like @Email) and business rules (like @Min(18)) at the field level whenever possible.
  • Keep client-side validation as a UX nicety, never as the only line of defense.
  • Group related constraints on a form object rather than duplicating checks in the controller.

Frequently Asked Questions

Spring throws a MethodArgumentNotValidException, which normally results in a 400 Bad Request response unless you handle it with an exception handler.

Yes - annotate the nested field with @Valid as well (for example @Valid private Address address;) so Bean Validation cascades into it.

Yes, by creating a custom annotation paired with a ConstraintValidator implementation, which lets you express rules that the built-in annotations do not cover.

Key Takeaways

  • Server-side validation is required because client-side checks can always be bypassed.
  • Bean Validation annotations like @NotBlank, @Size, and @Min declare rules directly on the form object.
  • @Valid triggers those checks when a bound object is passed into a handler method.
  • BindingResult, declared right after the @Valid parameter, captures errors instead of throwing an exception.
  • Template engines can render field-level errors directly from BindingResult with no extra wiring.

Summary

Validation keeps bad data from ever reaching your service and persistence layers. But not every failure is a validation problem - sometimes a database call fails, or a resource simply does not exist. Next, you will learn how Spring MVC centralizes handling for exceptions like those.

Next Lesson →

Exception Handling in Spring MVC