Contactez-nous

Validation des données d'entrée avec Bean Validation (JSR 380)

Apprenez à utiliser Bean Validation (JSR 380/Jakarta Bean Validation) avec Spring Boot pour valider automatiquement les données d'entrée via des annotations (@NotNull, @Size, @Valid...).

Pourquoi valider ? L'importance du contrôle des entrées

Dans toute application qui interagit avec l'extérieur (utilisateurs via une interface web, autres systèmes via des API REST, etc.), il est absolument fondamental de valider les données d'entrée. Ne jamais faire confiance aux données reçues est un principe de base en développement logiciel. La validation permet d'assurer plusieurs objectifs critiques :

  • Intégrité des données : Garantir que les données stockées dans votre système sont cohérentes, correctes et conformes aux règles métier (par exemple, une adresse e-mail doit avoir un format valide, un âge doit être positif, un champ obligatoire ne doit pas être vide).
  • Sécurité : Empêcher l'injection de données malveillantes ou inattendues qui pourraient conduire à des failles de sécurité (injection SQL, cross-site scripting - XSS) ou à des comportements erronés de l'application.
  • Expérience Utilisateur/Client : Fournir un retour clair et rapide à l'utilisateur ou au client de l'API lorsque les données soumises sont incorrectes, au lieu de laisser l'application échouer de manière inattendue plus tard dans le processus.
  • Robustesse : Eviter les exceptions non contrôlées (comme les NullPointerException) dues à des données manquantes ou invalides.

Effectuer cette validation manuellement dans chaque contrôleur ou service peut devenir fastidieux, répétitif et sujet aux erreurs. Heureusement, l'écosystème Java propose une solution standardisée : Bean Validation.

Bean Validation : le standard Java pour la validation

Bean Validation (initialement défini par la JSR 303, puis affiné par les JSR 349 et JSR 380, et maintenant partie de Jakarta EE sous le nom de Jakarta Bean Validation) est une spécification Java qui définit un modèle basé sur les annotations pour valider les objets Java (les "Beans"). L'idée est de déclarer les contraintes de validation directement sur les champs des classes de modèle (souvent des DTOs - Data Transfer Objects - ou des entités JPA) à l'aide d'annotations.

Il s'agit d'une spécification, ce qui signifie qu'elle définit l'API (les annotations comme @NotNull, @Size, @Pattern, etc., et les interfaces de validation) mais pas l'implémentation. L'implémentation la plus courante et la référence standard est Hibernate Validator. Spring Boot utilise Hibernate Validator par défaut lorsqu'il détecte Bean Validation sur le classpath.

Pour utiliser Bean Validation dans un projet Spring Boot, il suffit généralement d'avoir la dépendance spring-boot-starter-validation. Souvent, cette dépendance est déjà incluse transitivement par d'autres starters comme spring-boot-starter-web, donc vous n'avez peut-être rien à ajouter explicitement si vous développez une application web.

Appliquer les contraintes : les annotations de validation

Le coeur de Bean Validation réside dans l'application d'annotations sur les champs (ou parfois les méthodes getter ou les paramètres de constructeur) de vos classes Java que vous souhaitez valider. Voici quelques-unes des annotations standard les plus utilisées (provenant du package jakarta.validation.constraints.* ou javax.validation.constraints.* pour les anciennes versions) :

  • @NotNull : Vérifie que la valeur annotée n'est pas null.
  • @NotEmpty : Vérifie qu'une chaîne, une collection, une map ou un tableau n'est pas null et n'est pas vide.
  • @NotBlank : Vérifie qu'une chaîne n'est pas null et n'est pas composée uniquement d'espaces blancs (trim().length() > 0). Spécifique aux chaînes.
  • @Size(min=..., max=...) : Vérifie que la taille d'une chaîne, d'une collection, d'une map ou d'un tableau est comprise entre min et max.
  • @Min(value=...) : Vérifie qu'un nombre (ou sa représentation textuelle) est supérieur ou égal à value.
  • @Max(value=...) : Vérifie qu'un nombre (ou sa représentation textuelle) est inférieur ou égal à value.
  • @Email : Vérifie que la chaîne représente une adresse e-mail syntaxiquement valide (selon une expression régulière intégrée, mais souvent suffisante).
  • @Pattern(regexp=...) : Vérifie que la chaîne correspond à l'expression régulière Java spécifiée.
  • @Past / @Future : Vérifie qu'une date est dans le passé / futur.
  • @Valid : Utilisé pour déclencher la validation en cascade sur un objet associé (par exemple, un objet imbriqué dans un DTO).

Exemple d'un DTO avec des annotations de validation :

import jakarta.validation.Valid;
import jakarta.validation.constraints.*;

public class UserRegistrationDto {

    @NotBlank(message = "Le nom d'utilisateur ne peut pas être vide.")
    @Size(min = 3, max = 20, message = "Le nom d'utilisateur doit contenir entre {min} et {max} caractères.")
    private String username;

    @NotBlank(message = "Le mot de passe est requis.")
    @Size(min = 8, message = "Le mot de passe doit contenir au moins {min} caractères.")
    private String password;

    @NotBlank(message = "L'email est requis.")
    @Email(message = "Veuillez fournir une adresse email valide.")
    private String email;

    @Min(value = 18, message = "Vous devez avoir au moins {value} ans.")
    private Integer age;

    @Valid // Important pour valider l'objet AddressDto imbriqué
    @NotNull(message = "L'adresse est requise.")
    private AddressDto address;

    // Getters et Setters...

    public static class AddressDto {
        @NotBlank(message = "La rue est requise.")
        private String street;

        @NotBlank(message = "La ville est requise.")
        private String city;
        
        // Getters et Setters...
    }
}

Notez l'utilisation de l'attribut message pour personnaliser les messages d'erreur qui seront renvoyés si la validation échoue. Vous pouvez utiliser des placeholders comme {min}, {max}, {value} qui seront remplacés par les valeurs de l'annotation.

Déclencher la validation dans Spring Boot : l'annotation @Valid

Avoir des annotations sur votre DTO ne suffit pas ; il faut indiquer à Spring Boot quand effectuer la validation. Cela se fait principalement au niveau des contrôleurs, en utilisant l'annotation @Valid (provenant de jakarta.validation.Valid ou javax.validation.Valid).

Pour les API REST (avec @RequestBody) :
Placez l'annotation @Valid juste avant le paramètre annoté avec @RequestBody. Lorsque Spring reçoit la requête HTTP, il désérialise le corps de la requête (par exemple, du JSON) en un objet de votre DTO, puis, grâce à @Valid, il déclenche automatiquement le processus de validation de Bean Validation sur cet objet avant que la méthode du contrôleur ne soit exécutée.

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import jakarta.validation.Valid;

@RestController
public class UserController {

    @PostMapping("/register")
    public ResponseEntity registerUser(@Valid @RequestBody UserRegistrationDto userDto) {
        // Si on arrive ici, c'est que la validation a réussi.
        // Logique d'enregistrement de l'utilisateur...
        System.out.println("Utilisateur valide reçu: " + userDto.getUsername());
        return ResponseEntity.ok("Utilisateur enregistré avec succès !");
    }
}

Pour les applications web traditionnelles (avec @ModelAttribute) :
De manière similaire, placez @Valid avant le paramètre annoté avec @ModelAttribute (utilisé pour lier les données d'un formulaire HTML à un objet). La validation sera également effectuée automatiquement par Spring MVC.

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import jakarta.validation.Valid;

@Controller
public class RegistrationController {

    @GetMapping("/register-form")
    public String showForm(Model model) {
        model.addAttribute("userRegistrationDto", new UserRegistrationDto());
        return "registration-form"; // Nom de la vue Thymeleaf/JSP
    }

    @PostMapping("/process-registration")
    public String processRegistration(@Valid @ModelAttribute("userRegistrationDto") UserRegistrationDto userDto,
                                      BindingResult bindingResult) { // Important: BindingResult juste après l'objet validé
        
        if (bindingResult.hasErrors()) {
            // Si des erreurs de validation existent, retourner au formulaire
            return "registration-form"; 
        }
        
        // Validation réussie, traiter l'enregistrement...
        System.out.println("Enregistrement traité pour: " + userDto.getUsername());
        return "redirect:/success"; // Rediriger vers une page de succès
    }
}

Il est important de noter que dans le cas de @ModelAttribute, pour pouvoir gérer les erreurs de validation et réafficher le formulaire, vous devez ajouter un paramètre de type BindingResult immédiatement après le paramètre annoté avec @Valid. Spring y placera les résultats de la validation.

L'annotation @Validated (de org.springframework.validation.annotation.Validated) est une alternative spécifique à Spring qui peut aussi être utilisée. Elle est nécessaire notamment pour activer la validation par groupes (une fonctionnalité plus avancée de Bean Validation).