Veremos varias anotaciones de spring-boot-starter-validation y como validar la informaci贸n que llega a nuestros servicios antes de ejecutar procesos 馃懡
| Anotaci贸n | Descripci贸n |
|---|
@NotNull | Verifica que un objeto no sea nulo |
@NotEmpty | Verifica que el objeto no sea nulo y no est茅 vac铆o |
@NotBlank | Verifica que un objeto no sea nulo y no contenga solo caracteres en blanco |
@Size | Verifica que un String o Iterable tenga tama帽os l铆mite |
@Max | Verifica que el n煤mero sea >= (Mayor igual) al indicado |
@Min | Verifica que el n煤mero sea =< (Menor igual) al indicado |
@Pattern | Verifica que el objeto coincida con un regex |
@Past | Verifica que la fecha sea menor a la actual |
@Future | Verifica que la fecha sea mayor a la actual |
@Digits | Asegura que el valor num茅rico del campo tenga un n煤mero espec铆fico de d铆gitos enteros y fraccionarios |
@DecimalMin | Asegura que el valor decimal sea >= (Mayor igual) al especificado. |
@DecimalMax | Asegura que el valor decimal sea =< (Menor igual) al especificado. |
@AssertTrue | Asegura que el valor sea true. |
@AssertFalse | Asegura que el valor sea siempre false. |
@Positive | Asegura el que valor sea positivo. |
@PositiveOrZero | Asegura el que valor sea positivo o cero. |
@Negative | Asegura el que valor sea negativo. |
@NegativeOrZero | Asegura el que valor sea negativo o cero. |
@URL | Asegura que el valor sea un URL v谩lida. |
@Negative | Asegura el que valor sea negativo. |
Para usar estas anotaciones y validar la informaci贸n, seguiremos los siguientes pasos:
- Agregar la dependencia spring-boot-starter-validation en nuestro pom.xml
1
2
3
4
| <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
|
- Debemos anotar con
@Valid el dto.
1
2
3
4
5
| @DeleteMapping
@ResponseStatus(HttpStatus.NO_CONTENT)
public Mono<Void> delete(@RequestBody @Valid final QuotaCodRequestDto quotaCodRequestDto) {
return this.quotaService.delete(quotaCodRequestDto);
}
|
- Si queremos validar
@PathVariable o @RequestParam, debemos agregar la anotaci贸n @Validated en nuestro controlador
1
2
3
4
| @Validated
public class QuotaController {
...
}
|
Para cada anotaci贸n podemos indicar un mensaje de error en caso de que la validaci贸n no se cumpla
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| public record QuotaFilterRequestDto(
@Size(max = 50, message = "El campo description tiene un m\u00e1ximo de {max} caracteres")
String description,
Boolean finished,
@NotNull(message = "El campo pago es obligatorio")
@Min(value = 0, message = "El campo pageNo tiene un m铆nimo de {value}")
Integer pageNo,
@NotNull(message = "El campo pageSize es obligatorio")
@Min(value = 1, message = "El campo pageSize tiene un m铆nimo de {value}")
@Max(value = 10, message = "El campo pageSize tiene un m谩ximo de {value}")
Integer pageSize
) {
}
|
Podemos usar placeholders {} en los mensajes de error, esto facilita el que si tenemos que cambiar alg煤n valor de la validaci贸n, no nos preocupemos por cambiar en el mensaje tambi茅n. 馃
Si queremos capturar el mensaje de error para devolverlo como respuesta en el api, lo haremos con un @RestControllerAdvice
1
2
| public record ErrorMessageResponseDto(String message, Instant timestamp) {
}
|
Usaremos este dto para devolver informaci贸n sobre los errores de validaci贸n.
- Capturar errores de
@RequestBody + @Valid
1
2
3
4
5
6
7
8
9
10
| @RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorMessageResponseDto> handleMethodValidationExceptions(final MethodArgumentNotValidException ex) {
final String errorMessage = ex.getBindingResult().getAllErrors().stream().findFirst()
.map(DefaultMessageSourceResolvable::getDefaultMessage).orElse("Validation error unexpected");
return new ResponseEntity<>(new ErrorMessageResponseDto(errorMessage, Instant.now()), HttpStatus.UNPROCESSABLE_ENTITY);
}
}
|
- Capturar errores de
@RequestParam, @PathVariable + @Validated
1
2
3
4
5
6
7
8
9
10
| @RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<ErrorMessageResponseDto> handleConstraintViolationExceptions(final ConstraintViolationException ex) {
final String errorMessage = ex.getConstraintViolations().stream().findFirst()
.map(ConstraintViolation::getMessage).orElse("Validation error unexpected");
return new ResponseEntity<>(new ErrorMessageResponseDto(errorMessage, Instant.now()), HttpStatus.UNPROCESSABLE_ENTITY);
}
}
|
Al validar @RequestBody + @Valid la excepci贸n es del tipo MethodArgumentNotValidException. Y si validamos @RequestParam, @PathVariable + @Validated la excepci贸n es ConstraintViolationException 馃挕
Por defecto @Valid 煤nicamente verifica y valida los campos de primer nivel del dto.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| @Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class AdminRegisterRequestDto implements Serializable {
@NotBlank(message = "El nombre es obligatorio")
@Pattern(regexp = "^[a-zA-Z\\s]{5,100}$", message = "El nombre solo permite letras")
private String name;
@NotBlank(message = "El apellido es obligatorio")
@Pattern(regexp = "^[a-zA-Z\\s]{5,100}$", message = "El apellido solo permite letras")
private String lastName;
@NotNull(message = "Los datos de usuario son obligatorios")
private UsuarioRegisterDto user;
}
|
Por ejemplo, aqu铆 煤nicamente se validar谩n los campos name y lastName, si el dto UsuarioRegisterDto tambi茅n tiene validaciones no se tomar谩n en cuenta.
Para validar dto鈥檚 anidados debemos anotarlos con @Valid, esto garantiza que se validen los campos de ese dto.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| @Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class AdminRegisterRequestDto implements Serializable {
@NotBlank(message = "El nombre es obligatorio")
@Pattern(regexp = "^[a-zA-Z\\s]{5,100}$", message = "El nombre solo permite letras")
private String name;
@NotBlank(message = "El apellido es obligatorio")
@Pattern(regexp = "^[a-zA-Z\\s]{5,100}$", message = "El apellido solo permite letras")
private String lastName;
@Valid
@NotNull(message = "Los datos de usuario son obligatorios")
private UsuarioRegisterDto user;
}
|
- Garantiza que se trabaje con informaci贸n limpia, evitando errores inesperados.
- Optimiza el uso de recursos, ya que si los datos no pasan la validaci贸n nunca llega a ejecutar l贸gica interna (Consultar base de datos, llamar a servicios terceros, etc.)
- Informamos los errores de manera detallada siguiendo est谩ndares y buenas pr谩cticas.
Observaciones
- No todas las anotaciones se pueden usar con todos los tipos de datos. Por ejemplo si tratamos de validar con
@Past un Integer obtendremos una excepci贸n
1
| jakarta.validation.UnexpectedTypeException: HV000030: No validator could be found for constraint 'jakarta.validation.constraints.Past' validating type 'java.lang.Integer'. Check configuration for 'pageNo'
|