Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 69

Validation des données d'API avec Jakarta EE

Recommended resources

Bonjour ! Dans la leçon précédente, nous avons mis en place un GlobalExceptionHandler pour gérer nos erreurs de manière centralisée et professionnelle, en retournant des réponses claires et des codes de statut HTTP appropriés, comme le 404 Not Found.

Aujourd'hui, nous allons renforcer notre API en nous attaquant à un autre aspect fondamental de la robustesse : la validation des données d'entrée. L'objectif est de s'assurer que les données envoyées à notre API, par exemple lors de la création d'un nouveau livre, sont valides avant même que notre logique métier ne soit exécutée. Nous apprendrons à rejeter les requêtes incorrectes à la source, en retournant une erreur 400 Bad Request explicite.

Pour ce faire, nous allons explorer la spécification Jakarta Bean Validation et voir comment Spring Boot l'intègre de manière transparente pour nous permettre de valider des objets avec de simples annotations comme @NotNull et @Size.

1. Pourquoi valider les données en amont ?

Imaginez que quelqu'un essaie de créer un livre en envoyant un corps de requête JSON où le titre est manquant ou vide. Sans validation, cette donnée invalide pourrait se propager jusqu'à votre couche de service, voire jusqu'à la base de données, provoquant potentiellement une NullPointerException inattendue ou l'enregistrement de données corrompues.

La validation en amont agit comme un garde à l'entrée de votre API. Elle vérifie la qualité des données dès leur arrivée et rejette immédiatement ce qui est invalide.

Ce diagramme illustre parfaitement le rôle de la validation. Une requête avec des données invalides est interceptée et une erreur est retournée au client sans jamais atteindre le Repository ou la logique métier plus profonde.

Cette pratique est essentielle pour plusieurs raisons :

  • Sécurité : Empêche les données malformées d'exploiter des failles potentielles.
  • Intégrité des données : Garantit que seules des données conformes sont stockées.
  • Robustesse : Évite des erreurs inattendues plus loin dans le code.
  • Expérience développeur : Fournit un retour immédiat et clair au client de l'API sur ce qui ne va pas dans sa requête.

2. Intégrer Jakarta Bean Validation

Spring Boot facilite grandement l'utilisation de la validation. La spécification est Jakarta Bean Validation, et l'implémentation de référence, Hibernate Validator, est incluse par défaut lorsque vous utilisez la dépendance spring-boot-starter-web.

Développer des API REST avec Spring Boot : validation des ... - Cursa

Cet article de Cursa résume ce qu'est Bean Validation et son intégration dans Spring Boot.

Lisez les sections "Qu'est-ce que la validation du bean ?" et "Intégration de la validation des beans avec Spring Boot". Vous y verrez la confirmation que la dépendance spring-boot-starter-validation est la clé de cette fonctionnalité.

Le schéma suivant montre comment ces composants s'articulent :

Ce diagramme montre que votre application Spring Boot utilise le `spring-boot-starter-validation`, qui intègre Hibernate Validator, l'implémentation de l'API standard Jakarta Bean Validation.

3. Appliquer des règles de validation sur nos DTOs

La validation se fait en ajoutant des annotations directement sur les champs de nos objets de transfert de données (DTOs). Créons un DTO spécifique pour la création et la mise à jour de livres, que nous appellerons BookRequestDto.java dans votre package dto.

package com.votreprojet.dto;

public record BookRequestDto(
    String title,
    String author,
    Integer publicationYear
) {}

Maintenant, ajoutons des contraintes de validation. Pour cela, nous allons utiliser les annotations du package jakarta.validation.constraints.

La vidéo suivante montre en détail comment annoter une classe. Nous allons appliquer les mêmes principes à notre BookRequestDto.

Spring Boot Data Validation : Validez les INPUTS sur vos APIs (TUTO COMPLET)

Cette vidéo de "Coding With Armand" est un excellent guide pratique pour mettre en place la validation. L'exemple utilise une entité Car, mais les annotations sont universelles.

Regardez la section sur l'application des annotations. Concentrez-vous sur l'utilisation de @NotNull, @NotBlank, @Size, @Min, @Max, @Positive. Notez également comment ajouter un message personnalisé à chaque annotation pour des erreurs plus claires.

En nous inspirant de la vidéo, modifions notre BookRequestDto.java :

package com.votreprojet.dto;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.PastOrPresent;
import jakarta.validation.constraints.Size;

public record BookRequestDto(
    @NotBlank(message = "Le titre ne peut pas être vide ou contenir uniquement des espaces")
    @Size(min = 2, max = 100, message = "Le titre doit contenir entre 2 et 100 caractères")
    String title,

    @NotBlank(message = "Le nom de l'auteur ne peut pas être vide")
    String author,

    @NotNull(message = "L'année de publication est obligatoire")
    @PastOrPresent(message = "L'année de publication ne peut pas être dans le futur")
    Integer publicationYear
) {}

Analyse des annotations utilisées :

  • @NotBlank : S'assure que la chaîne n'est pas null et qu'elle contient au moins un caractère qui n'est pas un espace. C'est souvent plus strict et plus utile que @NotEmpty pour les champs texte.
  • @Size : Valide que la taille du champ (ici, la longueur du String) est dans une plage donnée.
  • @NotNull : Vérifie simplement que la valeur n'est pas null. Parfait pour les objets comme Integer.
  • @PastOrPresent : Une annotation de date/heure qui vérifie que la valeur est dans le passé ou le présent.

4. Activer la validation dans le contrôleur

Avoir des annotations sur notre DTO ne suffit pas. Il faut indiquer à Spring de les vérifier au moment où une requête arrive. Pour cela, on utilise l'annotation @Valid sur le paramètre du contrôleur qui reçoit les données.

Mise en place d'une API Rest avec Spring

Cette ressource est un TP qui contient une instruction très directe pour activer la validation.

Repérez la section "Ajouter des données" et lisez le "Travail à faire" qui vous demande d'ajouter @Valid à l'argument de la méthode postMovie. C'est exactement ce que nous allons faire.

Modifions notre BookController.java pour les méthodes de création et de mise à jour. Assurez-vous d'utiliser le nouveau BookRequestDto et d'injecter @Valid.

// Dans BookController.java
// ...
import jakarta.validation.Valid;

@RestController
@RequestMapping("/api/books")
public class BookController {

    // ...

    @PostMapping
    public ResponseEntity<BookDto> createBook(@Valid @RequestBody BookRequestDto bookRequest) {
        BookDto createdBook = bookService.createBook(bookRequest);
        return new ResponseEntity<>(createdBook, HttpStatus.CREATED);
    }

    @PutMapping("/{id}")
    public ResponseEntity<BookDto> updateBook(@PathVariable Long id, @Valid @RequestBody BookRequestDto bookRequest) {
        BookDto updatedBook = bookService.updateBook(id, bookRequest);
        return new ResponseEntity<>(updatedBook, HttpStatus.OK);
    }
    
    // ...
}

Avec @Valid en place, si un client envoie une requête avec des données invalides (par exemple, un JSON sans le champ title), Spring lèvera automatiquement une MethodArgumentNotValidException.


5. Gérer les erreurs de validation proprement

À ce stade, une requête invalide provoquerait une erreur 400 Bad Request, mais avec un corps de réponse générique et peu lisible généré par Spring. Nous pouvons faire bien mieux en utilisant le GlobalExceptionHandler que nous avons créé dans la leçon précédente !

Nous allons l'enrichir pour qu'il intercepte MethodArgumentNotValidException et retourne une liste structurée des erreurs de validation.

Spring Boot Data Validation : Validez les INPUTS sur vos APIs (TUTO COMPLET)

La même vidéo de "Coding With Armand" montre comment créer un gestionnaire d'exceptions pour la validation.

Regardez la section sur la création du contrôleur advice. Observez comment il crée un @ExceptionHandler pour MethodArgumentNotValidException, parcourt les FieldErrors pour extraire le nom du champ et le message d'erreur par défaut, puis construit une Map pour la réponse JSON.

Appliquons cette logique à notre GlobalExceptionHandler.java.

// Dans GlobalExceptionHandler.java
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.validation.FieldError;
import java.util.HashMap;
import java.util.Map;

// ...

@ControllerAdvice
public class GlobalExceptionHandler {

    // ... (gardez les gestionnaires précédents pour ResourceNotFoundException et Exception)

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<Map<String, String>> handleValidationExceptions(MethodArgumentNotValidException ex) {
        Map<String, String> errors = new HashMap<>();
        ex.getBindingResult().getAllErrors().forEach((error) -> {
            String fieldName = ((FieldError) error).getField();
            String errorMessage = error.getDefaultMessage();
            errors.put(fieldName, errorMessage);
        });
        return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST);
    }
}

Analyse du code :

  1. On crée un ExceptionHandler qui cible MethodArgumentNotValidException.
  2. On initialise une Map<String, String> pour stocker les erreurs (nom du champ -> message d'erreur).
  3. ex.getBindingResult().getAllErrors() nous donne la liste de toutes les erreurs de validation.
  4. On parcourt cette liste. Pour chaque erreur, on la caste en FieldError pour obtenir le nom du champ (getField()) et on récupère le message d'erreur (getDefaultMessage()). Ce message est celui que nous avons défini dans l'annotation sur le DTO !
  5. On retourne la Map d'erreurs avec un statut 400 Bad Request.

Maintenant, si vous testez avec Postman en envoyant une requête de création avec un corps vide {} à /api/books, vous obtiendrez une réponse 400 Bad Request claire et utile :

{
    "author": "Le nom de l'auteur ne peut pas être vide",
    "publicationYear": "L'année de publication est obligatoire",
    "title": "Le titre ne peut pas être vide ou contenir uniquement des espaces"
}

Le client de l'API sait exactement ce qu'il doit corriger.


Conclusion

Félicitations ! Vous avez ajouté une couche de validation robuste à votre API, la rendant plus sûre et plus facile à utiliser.

Points clés à retenir :

  • Validation Déclarative : Les annotations de Jakarta Bean Validation (@NotBlank, @Size, etc.) permettent de définir des règles directement sur les DTOs, séparant la validation de la logique métier.
  • Déclenchement via @Valid : L'annotation @Valid sur un @RequestBody dans un contrôleur active le processus de validation.
  • Gestion d'Erreurs Spécifique : L'exception MethodArgumentNotValidException est levée en cas d'échec de la validation.
  • Retour Informatif : En interceptant cette exception dans un @ControllerAdvice, vous pouvez construire et renvoyer une réponse 400 Bad Request structurée qui guide l'utilisateur pour corriger sa requête.

Notre API est maintenant capable de gérer les cas où les ressources n'existent pas (404) et où les données d'entrée sont invalides (400). La prochaine étape logique est de s'assurer que tout ce comportement fonctionne comme prévu.

Dans la prochaine leçon, nous apprendrons à tester nos endpoints d'API REST à l'aide d'un client HTTP comme Postman. Nous verrons comment créer et organiser des requêtes pour vérifier les cas de succès, les erreurs 404, et nos nouvelles erreurs de validation 400.