Restricciones de Bean Validation y grupos de restricciones
Aplique anotaciones de Jakarta Bean Validation con agrupación para hacer cumplir reglas específicas del contexto.
Restricciones de Bean Validation y grupos de restricciones es una lección gratuita de Spring Boot 4 Complete Guide en CoddyKit. Esta es la lección 1 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de Spring Boot 4 Complete Guide, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de Spring Boot 4 Complete Guide incluye 4 lecciones en total.
Partes de esta lección aún no han sido traducidas y se muestran en inglés.
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.
Aprende Java con un tutor de IA — gratis
Escribe y ejecuta código real en tu navegador, obtén ayuda instantánea de un tutor de IA disponible 24/7 y continúa donde lo dejaste en la web o en la aplicación.
- Cursos
- 21
- Lecciones
- 84
Preguntas frecuentes
¿La lección «Restricciones de Bean Validation y grupos de restricciones» es gratis?
Sí — el texto completo de «Restricciones de Bean Validation y grupos de restricciones» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de Spring Boot 4 Complete Guide, actualiza a CoddyKit PRO. El curso de Spring Boot 4 Complete Guide incluye 4 lecciones en total.
¿Qué aprenderé en «Restricciones de Bean Validation y grupos de restricciones»?
Aplique anotaciones de Jakarta Bean Validation con agrupación para hacer cumplir reglas específicas del contexto. Practicas Spring Boot 4 Complete Guide con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.
¿Necesito experiencia previa para empezar Spring Boot 4 Complete Guide?
No se requiere experiencia previa. Spring Boot 4 Complete Guide en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 1 de 4.
¿Cuánto tiempo toma la lección «Restricciones de Bean Validation y grupos de restricciones»?
La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.
¿Puedo escribir y ejecutar código en esta lección de Spring Boot 4 Complete Guide?
Sí. Cada lección de Spring Boot 4 Complete Guide incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.
Todas las lecciones de este curso
- Restricciones de Bean Validation y grupos de restricciones
- Creación de anotaciones de restricciones personalizadas
- Gestión global de excepciones con @ControllerAdvice
- Respuestas Problem Detail según RFC 7807