Skip to content
Spring Boot sb validation 3 min read

Bean Validation Intro

Input you don’t validate is a bug waiting to happen. Jakarta Bean Validation is the standard, declarative way to enforce constraints on Java objects — you annotate fields with rules like @NotBlank or @Email, and the framework checks them for you. Spring Boot wires this in automatically, so a single @Valid annotation on a controller method rejects bad requests before your business logic ever runs.

Why declarative validation

Without validation you end up writing the same defensive if checks at the top of every method:

if (request.name() == null || request.name().isBlank()) {
    throw new IllegalArgumentException("name is required");
}
if (request.age() < 0) {
    throw new IllegalArgumentException("age must be positive");
}

That code is repetitive, easy to forget, and scattered across the codebase. Bean Validation moves these rules onto the data itself as annotations, so the constraint lives next to the field it protects and is enforced consistently everywhere the object is validated.

public record CreateUserRequest(
        @NotBlank String name,
        @Positive int age) {}

The specification and its implementation

It helps to separate two things:

LayerWhat it isExample
Jakarta Bean ValidationThe specification (JSR 380 / Jakarta Validation 3.0) — defines the annotations and APIjakarta.validation.constraints.*
Hibernate ValidatorThe reference implementation that actually evaluates the constraintsorg.hibernate.validator

You program against the standard jakarta.validation annotations; Hibernate Validator does the work behind the scenes. The two are independent of Hibernate ORM despite the shared name.

Warning: Always import from jakarta.validation.constraints, never the legacy javax.validation.*. Spring Boot 3.x runs on Jakarta EE 9+, and the old javax packages will not be on the classpath.

Adding the dependency

The starter pulls in the API and Hibernate Validator together. It is not included by spring-boot-starter-web by default in Spring Boot 3.x, so you must add it explicitly.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

Gradle:

implementation 'org.springframework.boot:spring-boot-starter-validation'

With spring-boot-starter-parent 3.5.x managing versions, you don’t specify a version yourself.

@Valid in a controller

Annotate a @RequestBody parameter with @Valid and Spring validates it before invoking the method body. If any constraint fails, the request is rejected with 400 Bad Request and the handler never runs.

import jakarta.validation.Valid;
import jakarta.validation.constraints.*;
import org.springframework.web.bind.annotation.*;

public record CreateUserRequest(
        @NotBlank String name,
        @Email String email,
        @Min(18) int age) {}

@RestController
@RequestMapping("/api/users")
public class UserController {

    @PostMapping
    public UserResponse create(@Valid @RequestBody CreateUserRequest request) {
        return service.create(request);  // only reached if validation passes
    }
}

Invalid request:

curl -X POST http://localhost:8080/api/users \
  -H "Content-Type: application/json" \
  -d '{"name":"","email":"not-an-email","age":12}'

Output (default Spring Boot error):

{
  "timestamp": "2026-06-13T10:15:30.123+00:00",
  "status": 400,
  "error": "Bad Request",
  "path": "/api/users"
}

That default response is terse — you’ll almost always want to translate failures into a clean, field-level JSON payload. See Handling Validation Errors.

How it integrates

Spring Boot auto-configures a LocalValidatorFactoryBean (a Validator) whenever the starter is on the classpath. Two mechanisms then drive validation:

  • For request bodies, the RequestResponseBodyMethodProcessor honors @Valid and throws MethodArgumentNotValidException on failure.
  • For method parameters like @RequestParam and @PathVariable, the MethodValidationPostProcessor kicks in when the class is annotated @Validated, throwing ConstraintViolationException.

You can also inject the Validator and validate any object programmatically:

@Service
@RequiredArgsConstructor
public class ImportService {
    private final jakarta.validation.Validator validator;

    public void process(CreateUserRequest req) {
        var violations = validator.validate(req);
        if (!violations.isEmpty()) {
            throw new jakarta.validation.ConstraintViolationException(violations);
        }
    }
}

Tip: @Valid (from jakarta.validation) triggers standard validation and cascades into nested objects. @Validated (from Spring) additionally supports validation groups and enables method-level parameter validation.

In This Section

Last updated June 13, 2026
Was this helpful?