0Pricing
Spring Boot 4 Complete Guide · Lesson

Bean Validation Constraints and Constraint Groups

Apply Jakarta Bean Validation annotations with grouping to enforce context-specific rules.

Bean Validation Constraints and Constraint Groups is a free Spring Boot 4 Complete Guide lesson on CoddyKit — lesson 1 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Spring Boot 4 Complete Guide learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

Why Bean Validation?

In a Spring Boot 4 application you constantly receive untrusted data: request bodies, query params, form submissions. Jakarta Bean Validation (the jakarta.validation API) lets you declare rules as annotations directly on your model fields instead of writing manual if checks everywhere.

  • Declarative — the rule lives next to the field it protects.
  • Centralized — Spring triggers validation automatically at the controller boundary.
  • Consistent — the same annotated class can be validated in the web layer, service layer, or persistence layer.

The reference implementation behind the API is Hibernate Validator, pulled in by the spring-boot-starter-validation dependency.

Adding the Starter

Validation is not bundled with the web starter anymore, so you must add it explicitly. Once present, Hibernate Validator is auto-configured and Spring wires a Validator bean for you.

Add the dependency to your pom.xml:

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

Annotating a DTO

You attach constraints to the fields of a Data Transfer Object. Each annotation carries an optional message and validation parameters.

  • @NotBlank — the string must contain at least one non-whitespace character.
  • @Email — must look like a valid email address.
  • @Size(min, max) — length must fall within range.
  • @Min / @Max — numeric bounds.
public class UserRegistrationRequest {

    @NotBlank(message = "Username is required")
    @Size(min = 3, max = 20)
    private String username;

    @NotBlank
    @Email(message = "Provide a valid email")
    private String email;

    @Min(value = 18, message = "Must be at least 18")
    private int age;

    // getters and setters
}

Triggering Validation with @Valid

Annotating the DTO alone does nothing. You must tell Spring to validate the argument by placing @Valid on the controller method parameter. If validation fails, Spring throws a MethodArgumentNotValidException before your method body ever runs, and returns a 400 Bad Request.

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

    @PostMapping
    public ResponseEntity<String> register(
            @Valid @RequestBody UserRegistrationRequest request) {
        // reaches here only if all constraints pass
        return ResponseEntity.ok("Registered " + request.getUsername());
    }
}

The Problem: One DTO, Many Contexts

Imagine the same UserRegistrationRequest is reused for two operations:

  • Create — the client must NOT send an id (the server generates it).
  • Update — the client MUST send an existing id to know what to modify.

A field that should be @Null on create but @NotNull on update cannot be expressed with plain annotations, because they always fire. This is exactly the problem constraint groups solve.

Defining Constraint Groups

A constraint group is just a marker interface — an empty interface used purely as a tag. You create one per validation context.

Every constraint annotation accepts a groups attribute. When you assign a constraint to a group, that constraint only runs when validation is requested for that group.

public interface OnCreate {}

public interface OnUpdate {}

Assigning Constraints to Groups

Now tag each constraint with the group(s) it belongs to. A constraint with no groups attribute implicitly belongs to the built-in Default group and runs unless you switch groups.

Here the id field obeys opposite rules depending on context:

public class ProductRequest {

    @Null(groups = OnCreate.class,
          message = "id must be empty when creating")
    @NotNull(groups = OnUpdate.class,
             message = "id is required when updating")
    private Long id;

    @NotBlank(groups = {OnCreate.class, OnUpdate.class})
    private String name;

    @Positive(groups = {OnCreate.class, OnUpdate.class})
    private BigDecimal price;

    // getters and setters
}

Selecting a Group with @Validated

Here is the crucial distinction: @Valid always validates the Default group only and cannot select a group. To activate a specific group you must use Spring's @Validated annotation, which accepts the target group class.

Each endpoint picks the group that matches its operation:

@RestController
@RequestMapping("/api/products")
public class ProductController {

    @PostMapping
    public ResponseEntity<Void> create(
            @Validated(OnCreate.class) @RequestBody ProductRequest req) {
        // @Null id, @NotBlank name, @Positive price enforced
        return ResponseEntity.status(HttpStatus.CREATED).build();
    }

    @PutMapping
    public ResponseEntity<Void> update(
            @Validated(OnUpdate.class) @RequestBody ProductRequest req) {
        // @NotNull id enforced instead
        return ResponseEntity.ok().build();
    }
}

@Valid vs @Validated

This pair trips up many developers, so commit it to memory:

  • @Valid — comes from jakarta.validation. Works on fields for nested/cascading validation, but cannot specify a group (uses Default).
  • @Validated — comes from org.springframework.validation.annotation. Accepts group classes, and also enables method-level validation on Spring beans. Cannot be placed on a field for cascading.

Rule of thumb: use @Validated(Group.class) at the controller parameter to pick a group; use @Valid on inner object fields to cascade into them.

Cascading and Group Sequences

Two advanced needs come up often:

  • Cascading — to validate a nested object, mark its field with @Valid. Without it, the nested object's constraints are skipped.
  • Ordering — a @GroupSequence runs groups in order and stops at the first group that fails, so you can enforce cheap checks before expensive ones.
@GroupSequence({First.class, Second.class})
public interface OrderedChecks {}

public class OrderRequest {

    @NotEmpty(groups = First.class)
    private List<@Valid LineItem> items; // @Valid cascades into each item

    @AssertTrue(groups = Second.class,
                message = "Total must match line items")
    public boolean isTotalConsistent() {
        return /* expensive cross-field check */ true;
    }
}

Reading the Errors

When a grouped validation fails, the resulting MethodArgumentNotValidException still carries the standard BindingResult. You can translate it into a clean JSON response with a @RestControllerAdvice handler, mapping each field to its message.

@RestControllerAdvice
public class ValidationExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Map<String, String> handle(MethodArgumentNotValidException ex) {
        Map<String, String> errors = new HashMap<>();
        ex.getBindingResult().getFieldErrors().forEach(err ->
                errors.put(err.getField(), err.getDefaultMessage()));
        return errors;
    }
}

Quick Check

Test your understanding of how Spring selects a constraint group.

Recap

You learned how to enforce context-specific validation rules with Jakarta Bean Validation in Spring Boot 4:

  • Add spring-boot-starter-validation, then annotate DTO fields with constraints like @NotBlank, @Email, @Size, @Min.
  • Trigger validation at the controller boundary; a failure yields a 400 via MethodArgumentNotValidException.
  • Constraint groups are marker interfaces assigned via each annotation's groups attribute, letting one DTO carry different rules per operation (e.g. @Null on create vs @NotNull on update).
  • Use @Validated(Group.class) to select a group — @Valid only runs the Default group and is for cascading into nested objects.
  • @GroupSequence orders groups and short-circuits on the first failure; @Valid on a collection cascades into each element.

Frequently asked questions

Is the “Bean Validation Constraints and Constraint Groups” lesson free?

Yes — the full text of “Bean Validation Constraints and Constraint Groups” is free to read here on the web, and the Spring Boot 4 Complete Guide course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Spring Boot 4 Complete Guide course, upgrade to CoddyKit PRO.

What will I learn in “Bean Validation Constraints and Constraint Groups”?

Apply Jakarta Bean Validation annotations with grouping to enforce context-specific rules. You practise Spring Boot 4 Complete Guide with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start Spring Boot 4 Complete Guide?

No prior experience is required. Spring Boot 4 Complete Guide on CoddyKit is structured for beginners through advanced learners; this is — lesson 1 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Bean Validation Constraints and Constraint Groups” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this Spring Boot 4 Complete Guide lesson?

Yes. Every Spring Boot 4 Complete Guide lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. Bean Validation Constraints and Constraint Groups
  2. Building Custom Constraint Annotations
  3. Global Exception Handling with @ControllerAdvice
  4. RFC 7807 Problem Detail Responses
← Back to Spring Boot 4 Complete Guide