Contactez-nous

Annotations de validation (`@NotNull`, `@Size`, `@Pattern`, etc.)

Découvrez comment utiliser les annotations standard de Jakarta Bean Validation (@NotNull, @Size, @Pattern, etc.) pour valider facilement les données dans vos DTOs et entités Spring Boot.

Pourquoi la validation des données est-elle cruciale ?

Assurer l'intégrité et la cohérence des données est fondamental dans toute application. Les données reçues des utilisateurs ou d'autres systèmes (via des formulaires web, des API REST, etc.) ne sont pas toujours fiables ou conformes aux attentes. Elles peuvent être manquantes, mal formatées, dépasser les limites autorisées, ou ne pas respecter les règles métier. Accepter et traiter des données invalides peut entraîner des erreurs d'exécution, des incohérences dans la base de données, des failles de sécurité et une mauvaise expérience utilisateur.

Il est donc essentiel de mettre en place une validation systématique des données dès leur réception par l'application, avant même de les traiter ou de les persister. Plutôt que d'écrire manuellement des blocs `if/else` complexes et répétitifs pour vérifier chaque champ dans vos contrôleurs ou services, l'écosystème Java propose une solution standardisée et élégante : Jakarta Bean Validation (anciennement Java Bean Validation ou JSR 380).

Cette spécification définit une API de métadonnées (basée sur des annotations) et une API de validation pour les objets Java (les "beans"). Spring Boot s'intègre parfaitement avec cette norme, notamment via l'implémentation de référence Hibernate Validator.

Intégration de Jakarta Bean Validation dans Spring Boot

L'intégration est quasi automatique. Lorsque vous utilisez le starter spring-boot-starter-web ou spring-boot-starter-validation, Spring Boot ajoute les dépendances nécessaires (jakarta.validation-api et généralement hibernate-validator) à votre projet.

Vous pouvez alors utiliser les annotations de contrainte définies par la spécification directement sur les champs de vos classes Java, typiquement vos DTOs (Data Transfer Objects) utilisés pour recevoir des données (@RequestBody) ou vos objets de formulaire (@ModelAttribute), mais aussi potentiellement sur vos entités JPA (@Entity) pour une validation au niveau de la couche de persistance (bien qu'il soit souvent préférable de valider plus tôt).

Le principe est simple : vous décorez les champs de vos classes avec des annotations qui décrivent les règles que ces champs doivent respecter.

Les annotations de contrainte les plus courantes

Jakarta Bean Validation fournit un ensemble riche d'annotations de contrainte standard (principalement dans le package jakarta.validation.constraints). Voici quelques-unes des plus utilisées :

  • Contraintes de nullité :
    • @NotNull : Le champ ne doit pas être null. Applicable à tout type d'objet.
    • @NotEmpty : Le champ ne doit pas être null et ne doit pas être vide. Applicable aux chaînes de caractères, collections, maps et tableaux (vérifie la taille/longueur > 0).
    • @NotBlank : Le champ ne doit pas être null et doit contenir au moins un caractère non blanc (espace). Applicable principalement aux chaînes de caractères.
  • Contraintes de taille/longueur :
    • @Size(min=..., max=...) : Vérifie que la taille (pour les collections, maps, tableaux) ou la longueur (pour les chaînes) est comprise entre min et max (inclus).
  • Contraintes numériques :
    • @Min(value=...) : La valeur numérique doit être supérieure ou égale à value.
    • @Max(value=...) : La valeur numérique doit être inférieure ou égale à value.
    • @Positive : La valeur numérique doit être strictement positive (> 0).
    • @PositiveOrZero : La valeur numérique doit être positive ou nulle (>= 0).
    • @Negative : La valeur numérique doit être strictement négative (< 0).
    • @NegativeOrZero : La valeur numérique doit être négative ou nulle (<= 0).
    • @Digits(integer=..., fraction=...) : Vérifie que le nombre a au maximum integer chiffres pour la partie entière et fraction chiffres pour la partie décimale.
  • Contraintes sur les dates :
    • @Past : La date doit être dans le passé.
    • @PastOrPresent : La date doit être dans le passé ou le présent.
    • @Future : La date doit être dans le futur.
    • @FutureOrPresent : La date doit être dans le futur ou le présent.
  • Autres contraintes utiles :
    • @Email : Vérifie que la chaîne de caractères ressemble à une adresse e-mail valide (basé sur une expression régulière simplifiée).
    • @Pattern(regexp=...) : Vérifie que la chaîne de caractères correspond à l'expression régulière Java spécifiée. Très puissant pour des formats personnalisés.
    • @AssertTrue / @AssertFalse : Appliqué à une méthode booléenne (getter-like) de la classe, vérifie que la méthode retourne true ou false. Utile pour des validations complexes impliquant plusieurs champs.
  • Attribut message : Toutes ces annotations acceptent un attribut message qui permet de personnaliser le message d'erreur retourné si la validation échoue. Exemple : @NotNull(message = "Le nom d'utilisateur ne peut pas être nul"). Vous pouvez utiliser des expressions comme {jakarta.validation.constraints.NotNull.message} pour référencer les messages par défaut ou internationalisés définis dans des fichiers de propriétés (ValidationMessages.properties).

Exemple d'utilisation sur un DTO

Voici comment appliquer ces annotations sur un DTO utilisé pour créer un utilisateur :

package com.certiquizz.monappliboot.dto;

import jakarta.validation.constraints.*;
import java.time.LocalDate;
import java.util.List;

public class CreateUserRequestDto {

    @NotBlank(message = "Le nom d'utilisateur est requis.")
    @Size(min = 3, max = 50, message = "Le nom d'utilisateur doit contenir entre {min} et {max} caractères.")
    private String username;

    @NotBlank(message = "L'email est requis.")
    @Email(message = "Le format de l'email est invalide.")
    private String email;

    @NotNull(message = "Le mot de passe est requis.")
    @Size(min = 8, message = "Le mot de passe doit contenir au moins {min} caractères.")
    // Pour des validations de mot de passe plus complexes (majuscules, chiffres, etc.), 
    // @Pattern ou une contrainte personnalisée serait nécessaire.
    private String password;

    @Past(message = "La date de naissance doit être dans le passé.")
    private LocalDate dateOfBirth; // Peut être null si non @NotNull

    @Size(max = 5, message = "Vous ne pouvez pas avoir plus de {max} rôles.")
    @NotEmpty(message = "Au moins un rôle doit être spécifié.") // La liste ne peut pas être nulle ou vide
    private List roles;

    @Min(value = 0, message = "Le score initial ne peut pas être négatif.")
    private Integer initialScore = 0; // Valeur par défaut

    @Pattern(regexp = "^[a-zA-Z0-9_]*$", message = "Le code de référence ne peut contenir que des lettres, chiffres et underscores.")
    private String referralCode; // Peut être null

    @AssertTrue(message = "L'utilisateur doit accepter les termes et conditions.")
    private boolean termsAccepted;

    // Constructeur, Getters, Setters...
    // Important: Assurez-vous que les getters existent pour que Hibernate Validator puisse accéder aux valeurs.

    public String getUsername() { return username; }
    public void setUsername(String username) { this.username = username; }
    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; }
    public LocalDate getDateOfBirth() { return dateOfBirth; }
    public void setDateOfBirth(LocalDate dateOfBirth) { this.dateOfBirth = dateOfBirth; }
    public List getRoles() { return roles; }
    public void setRoles(List roles) { this.roles = roles; }
    public Integer getInitialScore() { return initialScore; }
    public void setInitialScore(Integer initialScore) { this.initialScore = initialScore; }
    public String getReferralCode() { return referralCode; }
    public void setReferralCode(String referralCode) { this.referralCode = referralCode; }
    public boolean isTermsAccepted() { return termsAccepted; }
    public void setTermsAccepted(boolean termsAccepted) { this.termsAccepted = termsAccepted; }
}

Ces annotations définissent clairement les règles que l'objet CreateUserRequestDto doit respecter pour être considéré comme valide.

Déclencher la validation

Annoter vos classes ne suffit pas ; il faut indiquer à Spring Boot quand effectuer la validation. Cela se fait généralement dans la couche contrôleur en utilisant l'annotation @Valid (de jakarta.validation) sur le paramètre que vous souhaitez valider. Nous verrons cela plus en détail dans le chapitre suivant sur la validation dans les contrôleurs.

En résumé, les annotations de Jakarta Bean Validation fournissent un moyen standardisé, déclaratif et puissant pour définir les règles de validation directement sur vos objets de données. Associées à l'intégration de Spring Boot, elles permettent de construire des applications plus robustes et fiables en garantissant la qualité des données dès leur entrée dans le système.