Antes de meternos en el código, hagamos una pregunta sencilla pero importante: ¿para qué sirve la validación? Imagina que tu REST API es la entrada a una fiesta, y los datos de la petición son los invitados. No puedes dejar pasar a todo el mundo: primero hay que comprobar que los datos cumplen ciertos criterios (por ejemplo, edad mayor de 18, no hay cosas sospechosas como "NaN" en lugar del nombre). Si a la fiesta entran "invitados" inapropiados, la aplicación puede montarla, fallar con errores o incluso "incendiar el servidor".
La validación soluciona ese problema verificando los datos de la petición antes de procesarlos. Esto ayuda a evitar errores inútiles en el servidor, mantiene la integridad de los datos y facilita el debug.
Fundamentos del Bean Validation API
Spring usa la especificación Bean Validation API para implementar la comprobación de datos. Esa especificación se introdujo en Java EE 6 allá por 2009 (sí, es una magia muy antigua en términos de IT). En la práctica usamos la implementación Hibernate Validator, que viene con Spring Boot por defecto.
La idea principal de Bean Validation es usar anotaciones para indicar reglas de validación a nivel del modelo de datos (esos mismos classes DTO o entidades). Aquí tienes una lista de anotaciones populares:
| Anotación | Descripción |
|---|---|
@NotNull |
El campo no puede ser null |
@Size |
Indica la longitud mínima y máxima permitida de una cadena |
@Min |
Valor mínimo para campos numéricos |
@Max |
Valor máximo para campos numéricos |
@Email |
Verifica el formato de la cadena como correo electrónico |
@Pattern |
Permite especificar una expresión regular para validar una cadena |
Esas anotaciones se ponen en los campos y hacen la magia de la validación, pero su poder se desbloquea gracias a @Valid.
Aplicación de Bean Validation en Spring REST API
Ahora pasemos de la teoría a la práctica. Vamos a validar los datos entrantes en la petición. Como ejemplo crearemos un API sencillo para gestionar usuarios (porque a todos los API les gustan los usuarios).
Creación del DTO de usuario
DTO (Data Transfer Object) es la clase que transporta datos entre cliente y servidor. Primero creamos el DTO con anotaciones de validación:
package com.example.demo.dto;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
public class UserDTO {
@NotNull(message = "El nombre de usuario no puede estar vacío")
@Size(min = 2, max = 50, message = "El nombre de usuario debe tener entre 2 y 50 caracteres")
private String name;
@NotNull(message = "El correo electrónico es obligatorio")
@Email(message = "Formato de correo electrónico inválido")
private String email;
@NotNull(message = "La contraseña no puede estar vacía")
@Size(min = 6, message = "La contraseña debe tener al menos 6 caracteres")
private String password;
// Getters y setters...
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
Aquí fijamos restricciones para cada campo. Por ejemplo, el nombre de usuario debe tener entre 2 y 50 caracteres, y el email debe cumplir el formato adecuado.
Añadiendo validación en el controlador
La validación de datos ocurre automáticamente si pones la anotación @Valid delante del objeto en la petición. Vamos a crear un controlador para crear usuarios:
package com.example.demo.controller;
import com.example.demo.dto.UserDTO;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import jakarta.validation.Valid;
@RestController
@RequestMapping("/users")
public class UserController {
@PostMapping
public ResponseEntity<String> createUser(@Valid @RequestBody UserDTO user) {
// El objeto recibido ya pasó la validación si estamos aquí!
return new ResponseEntity<>("Usuario creado con éxito: " + user.getName(), HttpStatus.CREATED);
}
}
Entonces, ¿qué pasa aquí?
- Hemos añadido
@Validantes del parámetrouser. Esa anotación le indica a Spring que hay que validar el objeto antes de ejecutar el método. - Si los datos en
userno cumplen los requisitos, Spring genera automáticamente un error 400 (Bad Request) y devuelve la descripción del fallo.
Ahora vayamos a un escenario donde el cliente envía datos inválidos.
Ejemplo de petición incorrecta
El cliente envía un POST HTTP a /users con este body:
{
"name": "",
"email": "not-an-email",
"password": "123"
}
Spring reaccionará y devolverá en la respuesta un error con la descripción:
{
"timestamp": "2023-10-10T12:00:00.000+00:00",
"status": 400,
"errors": [
"El nombre de usuario no puede estar vacío",
"Formato de correo electrónico inválido",
"La contraseña debe tener al menos 6 caracteres"
]
}
Fíjate que los mensajes de error vienen de los campos message en las anotaciones de nuestro DTO.
Gestión de errores de validación
El manejo por defecto de errores no siempre es cómodo. Vamos a personalizarlo con @ExceptionHandler.
Creemos un manejador global de excepciones:
package com.example.demo.exception;
import jakarta.validation.ConstraintViolationException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.HashMap;
import java.util.Map;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, String>> handleValidationExceptions(MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
for (FieldError fieldError : ex.getBindingResult().getFieldErrors()) {
errors.put(fieldError.getField(), fieldError.getDefaultMessage());
}
return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST);
}
}
Ahora nuestra respuesta a una petición inválida tendrá este aspecto:
{
"name": "El nombre de usuario no puede estar vacío",
"email": "Formato de correo electrónico inválido",
"password": "La contraseña debe tener al menos 6 caracteres"
}
Esta forma de presentar los errores es más fácil de leer y procesar para los clientes.
Uso en la práctica y errores comunes
¿Dónde sirve esto?
- En entrevistas. Saber validar datos es una skill básica para cualquier backend developer. Seguro te preguntarán: "¿Cómo manejas datos inválidos en un API?"
- En proyectos reales. La validación protege el servidor de datos basura y facilita el mantenimiento del código.
Errores comunes
- Olvidar
@Valid. Si no pones@Valid, los datos simplemente no se validarán. - Mensajes de error poco claros. Procura escribir mensajes comprensibles y amigables para los usuarios.
- Restricciones solo en el código, no en la DB. Recuerda que la validación a nivel de código no sustituye las restricciones a nivel de base de datos, como NOT NULL o CHECK.
Ahora no solo sabes cómo validar datos en Spring, ¡sino también cómo evitar las trampas más típicas!
GO TO FULL VERSION