Validación de Datos en Spring Boot con Validadores de Bean y Personalizados

Validadores Integrados en Java

En el ecosistema Java, los paquetes javax.validation.constraints y org.hibernate.validator.constraints proporcionan anotaciones esenciales para validar campos de objetos Java. A continuación, se describen categorías clave y ejemplos de uso.

Antoaciones de javax.validation.constraints

  • Validación de nulidad: @Null (campo debe ser nulo), @NotNull (campo no puede ser nulo), @NotEmpty (colección o cadena no vacía), @NotBlank (cadena sin espacios en blanco y no vacía).
  • Validación de rango: @Size(min, max) (longitud de cadena o colección), @Min(value) y @Max(value) (límites numéricos), @DecimalMin y @DecimalMax (valores decimales como cadenas), @Digits(integer, fraction) (dígitos enteros y decimales).
  • Validación de booleanos: @AssertTrue (campo debe ser verdadero), @AssertFalse (campo debe ser falso).
  • Validación de fechas: @Future (fecha futura), @Past (fecha pasada), y variantes que incluyen el presente.
  • Otras validaciones: @Email (formato de correo), @Pattern(regexp) (expresión regular), @URL (dirección web válida, extensión de Hibernate).

Anotaciones de org.hibernate.validator.constraints

Este paquete incluye validaciones extendidas como @Length(min, max) para cadenas, @CreditCardNumber para números de tarjeta, @SafeHtml para prevenir ataques XSS, y @UniqueElements para garantizar elementos únicos en colecciones.

Ejemplo Práctico de Validación en una Clase Java


import javax.validation.constraints.*;
import org.hibernate.validator.constraints.*;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Collection;

public class Cliente {

    @NotNull(message = "El identificador es obligatorio")
    private Long identificador;

    @NotBlank(message = "El nombre de usuario no puede estar vacío")
    @Size(min = 2, max = 30, message = "El nombre debe tener entre 2 y 30 caracteres")
    private String nombreUsuario;

    @Min(value = 21, message = "La edad mínima es 21 años")
    @Max(value = 99, message = "La edad máxima es 99 años")
    private Integer edad;

    @DecimalMin(value = "1.00", message = "El saldo no puede ser inferior a 1.00")
    @DecimalMax(value = "50000.00", message = "El saldo no puede superar 50000.00")
    private BigDecimal saldo;

    @FutureOrPresent(message = "La fecha de alta debe ser actual o futura")
    private LocalDate fechaAlta;

    @Email(message = "Formato de correo inválido")
    private String correo;

    @Pattern(regexp = "^\\d{9}$", message = "El teléfono debe tener 9 dígitos")
    private String telefono;

    @Length(min = 8, max = 16, message = "La contraseña debe tener de 8 a 16 caracteres")
    private String contrasena;

    @UniqueElements(message = "Los intereses no pueden repetirse")
    private Collection<String> intereses;

    // Métodos de acceso (getters y setters)
}

Integración con Spring Boot

Para habilitar la validación en una aplicación Spring Boot, se requiere la dependencia spring-boot-starter-validation. Luego, se aplica la validación en controladores REST mediante anotaciones.

Configuración en un Controlador


import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;

@RestController
@RequestMapping("/clientes")
public class ClienteControlador {

    @PostMapping
    public String registrarCliente(@Valid @RequestBody Cliente cliente) {
        return "Cliente registrado exitosamente";
    }
}

Manejo Global de Excepciones

Para capturar errores de validación y devolver mensajes claros, se define un manejador global.


import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.LinkedHashMap;
import java.util.Map;

@RestControllerAdvice
public class ManejadorExcepcionesGlobal {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<Map<String, String>> manejarErroresValidacion(MethodArgumentNotValidException ex) {
        Map<String, String> errores = new LinkedHashMap<>();
        ex.getBindingResult().getFieldErrors().forEach(error -> {
            String campo = error.getField();
            String mensaje = error.getDefaultMessage();
            errores.put(campo, mensaje);
        });
        return ResponseEntity.badRequest().body(errores);
    }
}

Creación de un Validador Personalizado

Para validar que una cadena comience con un prefijo específico, se crea una anotación y su validador asociado.

Definición de la Anotación


import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = ValidadorPrefijo.class)
public @interface IniciaCon {
    String mensaje() default "La cadena debe iniciar con el prefijo especificado";
    Class<?>[] grupos() default {};
    Class<? extends Payload>[] consecuencias() default {};
    String valorPrefijo() default "";
}

Implementación del Validador


import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class ValidadorPrefijo implements ConstraintValidator<IniciaCon, String> {
    private String prefijoRequerido;

    @Override
    public void inicializar(IniciaCon anotacion) {
        this.prefijoRequerido = anotacion.valorPrefijo();
    }

    @Override
    public boolean esValido(String valor, ConstraintValidatorContext contexto) {
        if (valor == null) {
            return true; // Permitir nulos según lógica de negocio
        }
        boolean valido = valor.startsWith(prefijoRequerido);
        if (!valido) {
            contexto.disableDefaultConstraintViolation();
            contexto.buildConstraintViolationWithTemplate(
                "Se esperaba que empezara con '" + prefijoRequerido + "', pero se encontró: " + valor
            ).addConstraintViolation();
        }
        return valido;
    }
}

Uso de Validación por Grupos

Para escenarios donde la validación varía según la operación (ej., crear vs. actualizar), se definen interfaces de grupo.


public interface GrupoCreacion {}
public interface GrupoActualizacion {}

public class Cliente {
    @NotBlank(groups = GrupoCreacion.class, message = "El nombre es requerido para creación")
    @NotBlank(groups = GrupoActualizacion.class, message = "El nombre es requerido para actualización")
    private String nombreUsuario;

    @Null(groups = GrupoCreacion.class, message = "El ID debe ser nulo al crear")
    @NotNull(groups = GrupoActualizacion.class, message = "El ID es obligatorio al actualizar")
    private Long identificador;
}

// En el controlador
@PutMapping("/{id}")
public String actualizarCliente(
        @PathVariable Long id,
        @Validated(GrupoActualizacion.class) @RequestBody Cliente cliente) {
    return "Cliente actualizado";
}

Esta configuración permite una validación flexible y reutilizable en aplicaciones Spring Boot, adaptándose a diferentes requisitos de negocio.

Etiquetas: Spring Boot Bean Validation Hibernate Validator Custom Annotations Java Validation

Publicado el 7-5 02:03