Chaturmind
LearnDSASystem DesignDevOpsEngineering GrowthBlog
Start learning
Chaturmind

Structured learning paths for engineers who want to go deep. Written by practitioners.

Learn

  • Java
  • DSA
  • System Design
  • Spring Boot
  • AI / ML
  • DevOps
  • Engineering Growth

Company

  • Blog
  • Contact

Legal

  • Privacy Policy
  • Terms of Service

© 2026 Chaturmind. All rights reserved.

Built for engineers who want to go deep.


← Spring Boot REST API Development

Spring Boot Basics

  • What is Spring Boot?
  • Dependency Injection
  • Building REST Controllers

Validation & Error Handling

  • Bean Validation with @Valid
  • Global Exception Handling

Spring Framework Internals

  • IoC Container Fundamentals
  • Bean Lifecycle In Detail
  • Component Scanning & Configuration
  • Auto-Configuration Mechanism
  • Spring AOP
  • @Transactional Deep Dive

Microservices Architecture

  • Monolith to Microservices Decomposition
  • API Gateway
  • Service Discovery
  • Inter-Service Communication Choices
  • Event-Driven Architecture Patterns
  • Messaging Technology Choices

Resilience Patterns

  • Configuration Management
  • Circuit Breaker Pattern
  • Retry & Backoff Strategies
  • Bulkhead & Rate Limiting
  • Timeout Strategy
  • Why Microservices Fail

Distributed Data & Consistency Patterns

  • Two-Phase Commit
  • Saga Pattern
  • Outbox Pattern
  • Eventual Consistency Design
  • CQRS Basics
  • CAP Theorem

Observability & Operations

  • Centralized Logging
  • Distributed Tracing
  • Metrics & Monitoring
  • Alerting Strategy
  • Health Checks

Payment Systems

  • Payment — Requirements
  • Payment — Core Flow
  • Payment — Idempotency Implementation
  • Payment — Failure Handling & Reconciliation
  • Payment — Security

Order Management System

  • OMS — Requirements
  • OMS — State Machine Design
Chaturmind
← Spring Boot REST API Development

Spring Boot Basics

  • What is Spring Boot?
  • Dependency Injection
  • Building REST Controllers

Validation & Error Handling

  • Bean Validation with @Valid
  • Global Exception Handling

Spring Framework Internals

  • IoC Container Fundamentals
  • Bean Lifecycle In Detail
  • Component Scanning & Configuration
  • Auto-Configuration Mechanism
  • Spring AOP
  • @Transactional Deep Dive

Microservices Architecture

  • Monolith to Microservices Decomposition
  • API Gateway
  • Service Discovery
  • Inter-Service Communication Choices
  • Event-Driven Architecture Patterns
  • Messaging Technology Choices

Resilience Patterns

  • Configuration Management
  • Circuit Breaker Pattern
  • Retry & Backoff Strategies
  • Bulkhead & Rate Limiting
  • Timeout Strategy
  • Why Microservices Fail

Distributed Data & Consistency Patterns

  • Two-Phase Commit
  • Saga Pattern
  • Outbox Pattern
  • Eventual Consistency Design
  • CQRS Basics
  • CAP Theorem

Observability & Operations

  • Centralized Logging
  • Distributed Tracing
  • Metrics & Monitoring
  • Alerting Strategy
  • Health Checks

Payment Systems

  • Payment — Requirements
  • Payment — Core Flow
  • Payment — Idempotency Implementation
  • Payment — Failure Handling & Reconciliation
  • Payment — Security

Order Management System

  • OMS — Requirements
  • OMS — State Machine Design
HomeLearnSpring BootSpring Boot REST API DevelopmentValidation & Error Handling
✓ FreeIntermediate· 6 min read

Bean Validation with @Valid

@NotBlank, @Size, @Valid — validate request bodies without boilerplate.

Published September 21, 2026


Bean Validation with @Valid

Every API has to reject bad input: a blank name, a malformed email, a negative quantity. You could write if checks at the top of every controller method, but that code gets repeated, drifts out of sync, and buries the real logic. Bean Validation (the Jakarta Validation standard, implemented by Hibernate Validator) lets you declare the rules as annotations on the data class and have Spring enforce them automatically before your method runs.

Add the starter to enable it:

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

Declaring constraints on a request DTO

public record CreateUserRequest(
        @NotBlank(message = "Name is required")
        @Size(max = 100)
        String name,

        @NotBlank @Email(message = "Must be a valid email")
        String email,

        @NotBlank @Size(min = 8, message = "Password must be at least 8 characters")
        String password,

        @NotNull @Min(18) @Max(120)
        Integer age,

        @Valid                       // validate the nested object's own constraints too
        @NotNull
        AddressRequest address,

        @Size(max = 5)
        List<@NotBlank String> tags  // constraints on each element of a collection
) {}

Triggering validation with @Valid

Annotations alone do nothing. Something has to ask for validation. In a controller, that's @Valid on the parameter:

@PostMapping("/users")
@ResponseStatus(HttpStatus.CREATED)
public UserDto createUser(@RequestBody @Valid CreateUserRequest request) {
    return userService.create(request);    // only reached if every constraint passed
}

When a constraint fails, Spring doesn't call your method. It throws MethodArgumentNotValidException, which contains every failed field and message, and returns 400 Bad Request. Turning that into a clean, consistent error body is covered in Global Exception Handling.

The null question: @NotNull vs @NotEmpty vs @NotBlank

This trips up almost everyone, and it's a favourite interview question:

Constraintnull""" ""abc"Applies to
@NotNull❌✅✅✅Any type
@NotEmpty❌❌✅✅Strings, collections, maps, arrays
@NotBlank❌❌❌✅Strings only

There's a second, subtler rule: almost every other constraint treats null as valid. @Size(min = 8), @Email, @Pattern and @Min all pass when the value is null. So @Email String email alone accepts a missing email. If the field is required, combine it with @NotNull or @NotBlank. The design is intentional: it keeps "is it present?" separate from "is it well-formed?", so optional fields can still be format-checked when they are supplied.

Use wrapper types (Integer, not int) for required numeric fields. With int, a missing JSON field silently becomes 0, and @NotNull can never fire.

Common constraints

AnnotationMeaning
@Size(min, max)Length of a string, or size of a collection
@Min / @MaxNumeric bounds (inclusive)
@Positive / @PositiveOrZeroNumber > 0 / ≥ 0
@EmailSyntactically valid email (null passes)
@Pattern(regexp = "...")Matches a regular expression
@Past / @Future / @PastOrPresentDate/time relative to now
@Digits(integer, fraction)Numeric precision, e.g. for money
@ValidCascade: validate the constraints inside this nested object or collection

Nested objects and collections

Validation does not cascade automatically. If CreateUserRequest contains an AddressRequest, the address's own annotations are ignored unless the field is marked @Valid. It's easy to miss because nothing fails. The nested rules simply never run.

public record OrderRequest(
        @NotEmpty List<@Valid OrderLineRequest> lines    // validates each line's own constraints
) {}

Validating path variables and query parameters: @Validated

@Valid on a @RequestBody covers the JSON body. To validate simple parameters, put @Validated on the controller class. That switches on Spring's method validation:

@RestController
@Validated
public class ProductController {

    @GetMapping("/products")
    public Page<ProductDto> list(@RequestParam @Min(0) int page,
                                 @RequestParam @Max(100) int size) { ... }
}

Failures here arrive as a different exception: ConstraintViolationException (or, from Spring Framework 6.1, HandlerMethodValidationException). Your global handler has to map it to 400 too, otherwise it surfaces as a 500.

Writing your own constraint

When the built-in annotations aren't enough, write a constraint annotation plus a validator class:

@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.RECORD_COMPONENT})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = SlugValidator.class)
public @interface ValidSlug {
    String message() default "must contain only lowercase letters, digits and hyphens";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

public class SlugValidator implements ConstraintValidator<ValidSlug, String> {
    private static final Pattern SLUG = Pattern.compile("^[a-z0-9]+(-[a-z0-9]+)*$");

    @Override
    public boolean isValid(String value, ConstraintValidatorContext ctx) {
        return value == null || SLUG.matcher(value).matches();   // null handled by @NotBlank, by convention
    }
}

For rules that involve several fields (for example "endDate must be after startDate"), put a custom constraint on the class instead of a field. The validator then receives the whole object.

What Bean Validation should not do

Bean Validation checks that input is well-formed: it's present, the right length, the right format. It is the wrong place for business rules that need data:

  • "This email is not already registered" needs a database query.
  • "This product is in stock" depends on current state and can race with other requests.

Put those checks in the service layer, backed by the database (a unique index is the real guarantee against duplicates). Mixing them into validators makes validation slow, hard to test and still racy.

Groups: different rules for create vs update

Validation groups let one DTO carry different rules for different operations. For example, id must be null on create but present on update. They're powerful but add complexity. Many teams find separate CreateXRequest and UpdateXRequest records clearer than one class with groups.

Follow-up questions this topic invites — and their answers

Q: What's the difference between @Valid and @Validated? A: @Valid is the standard Jakarta annotation. It triggers validation of an object, and cascades into nested objects when placed on a field. @Validated is Spring's variant. It supports validation groups, and on a class it enables method-level validation of simple parameters such as @RequestParam and @PathVariable. In controllers you typically use @Valid on request bodies and @Validated on the class when you also validate parameters.

Q: Why does @Email accept null? A: By convention every constraint except the "not null/empty/blank" family treats null as valid. That keeps presence and format as separate concerns, so an optional field can still be format-checked when it's supplied. If the field is required, add @NotBlank or @NotNull alongside it.

Q: Can you use Bean Validation outside controllers, for example in a service? A: Yes. Annotate the service class with @Validated and its method parameters with constraints or @Valid, and Spring validates calls through the proxy. Note that, like @Transactional, it only works for calls coming through the Spring proxy, not for one method calling another in the same class. You can also inject a Validator and call validate(object) directly.

Q: How do you return all validation errors at once rather than just the first? A: Bean Validation already collects every violation. MethodArgumentNotValidException.getBindingResult().getFieldErrors() gives you all of them. Map them into a list or a field→message map in your exception handler, so the client can show every problem in one round trip.

Q: How do you translate validation messages into other languages? A: Use message keys instead of literal text, for example @NotBlank(message = "{user.name.required}"). Then define the texts in ValidationMessages.properties or Spring's messages_xx.properties files. The locale is resolved from the request's Accept-Language header.

Previous

Building REST Controllers

Next

Global Exception Handling

AI Tutor

Lesson: Bean Validation with @Valid

Quick actions

AI responses can be inaccurate. Verify critical information.