CodeGym /Cursos /Módulo 5. Spring /Validación de datos en REST API con la anotación @Valid

Validación de datos en REST API con la anotación @Valid

Módulo 5. Spring
Nivel 10 , Lección 8
Disponible

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í?

  1. Hemos añadido @Valid antes del parámetro user. Esa anotación le indica a Spring que hay que validar el objeto antes de ejecutar el método.
  2. Si los datos en user no 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!

Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION