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
idto 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 fromjakarta.validation. Works on fields for nested/cascading validation, but cannot specify a group (usesDefault).@Validated— comes fromorg.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
@GroupSequenceruns 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
groupsattribute, letting one DTO carry different rules per operation (e.g.@Nullon create vs@NotNullon update). - Use
@Validated(Group.class)to select a group —@Validonly runs theDefaultgroup and is for cascading into nested objects. @GroupSequenceorders groups and short-circuits on the first failure;@Validon 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
- Bean Validation Constraints and Constraint Groups
- Building Custom Constraint Annotations
- Global Exception Handling with @ControllerAdvice
- RFC 7807 Problem Detail Responses