Contactez-nous

Gestion des réponses (`ResponseEntity`, codes de statut HTTP)

Apprenez à construire des réponses HTTP précises et informatives dans Spring Boot en utilisant ResponseEntity pour contrôler le corps, les en-têtes et les codes de statut.

Au-delà du corps de la réponse : l'importance de la réponse HTTP complète

Lorsqu'un client envoie une requête à votre API REST, il ne s'attend pas seulement à recevoir des données dans le corps de la réponse. La réponse HTTP complète, incluant le code de statut et les en-têtes, fournit un contexte crucial sur le résultat de l'opération demandée. Un code de statut `200 OK` est très différent d'un `404 Not Found` ou d'un `500 Internal Server Error`. De même, des en-têtes comme `Location` (pour une ressource créée) ou `Content-Type` sont essentiels pour une communication correcte.

Si retourner directement un objet depuis une méthode de @RestController est pratique pour les cas simples (Spring se chargeant de la sérialisation et renvoyant un `200 OK` par défaut), cela ne vous donne pas le contrôle fin nécessaire pour gérer tous les scénarios RESTful (erreurs, création de ressources, réponses sans contenu, etc.). C'est là qu'intervient la classe ResponseEntity.

ResponseEntity : Contrôle total sur la réponse HTTP

ResponseEntity est une classe fournie par Spring Framework qui représente l'intégralité de la réponse HTTP : le code de statut, les en-têtes, et le corps (body). En retournant une instance de ResponseEntity depuis votre méthode de contrôleur, vous prenez le contrôle total sur ce qui sera envoyé au client.

Le type générique représente le type du corps de la réponse. Il peut s'agir de n'importe quel objet sérialisable (comme vos DTOs), de Void (pour les réponses sans corps), ou même de types plus génériques comme String ou Object. Spring utilisera toujours les HttpMessageConverter appropriés pour sérialiser le corps T, tout comme lorsque vous retournez T directement.

L'avantage principal de ResponseEntity est sa flexibilité pour définir précisément le code de statut et les en-têtes en fonction de la logique métier de votre méthode.

Définir les codes de statut HTTP

Le code de statut HTTP est l'information la plus critique après le corps de la réponse. Il indique au client si sa requête a réussi, a échoué, ou nécessite une action supplémentaire. ResponseEntity offre plusieurs façons de définir ce code :

  • Méthodes statiques pratiques : La classe fournit des méthodes statiques pour les codes de statut les plus courants, rendant le code très lisible. Elles retournent un `ResponseEntity.BodyBuilder` ou directement un `ResponseEntity`.
    • ResponseEntity.ok(body) ou ResponseEntity.ok().body(body) : Code 200 OK.
    • ResponseEntity.created(locationUri).body(body) : Code 201 Created, avec l'URI de la nouvelle ressource.
    • ResponseEntity.accepted().build() : Code 202 Accepted.
    • ResponseEntity.noContent().build() : Code 204 No Content (pas de corps).
    • ResponseEntity.badRequest().body(errorDetails) : Code 400 Bad Request.
    • ResponseEntity.notFound().build() : Code 404 Not Found (généralement sans corps).
    • ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error) : Code 500 Internal Server Error.
  • Méthode status() : Pour utiliser n'importe quel code de statut, vous pouvez utiliser la méthode status() du builder, qui accepte un entier ou une constante de l'enum HttpStatus.

Exemples :

import com.certiquizz.monappliboot.dto.UserDto;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.net.URI;

@RestController
@RequestMapping("/api/status-examples")
public class StatusController {

    @GetMapping("/{id}")
    public ResponseEntity getUser(@PathVariable Long id) {
        UserDto user = findUserById(id); // Logique métier
        if (user != null) {
            // Trouvé: 200 OK avec l'utilisateur dans le corps
            return ResponseEntity.ok(user);
            // Alternative: return new ResponseEntity<>(user, HttpStatus.OK);
        } else {
            // Non trouvé: 404 Not Found, pas de corps
            return ResponseEntity.notFound().build();
            // Alternative: return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
            // Alternative: return new ResponseEntity<>(HttpStatus.NOT_FOUND);
        }
    }

    @PostMapping
    public ResponseEntity createUser(@RequestBody UserDto newUser) {
        if (newUser.getUsername() == null || newUser.getUsername().isEmpty()) {
            // Données invalides: 400 Bad Request
            // On pourrait ajouter un corps avec les détails de l'erreur
            return ResponseEntity.badRequest().build();
        }
        UserDto createdUser = saveUser(newUser); // Logique métier
        // Créé: 201 Created avec l'utilisateur dans le corps et l'URI dans Location
        URI location = URI.create("/api/status-examples/" + createdUser.getId());
        return ResponseEntity.created(location).body(createdUser);
    }

    @DeleteMapping("/{id}")
    public ResponseEntity deleteUser(@PathVariable Long id) {
        boolean deleted = deleteUserById(id); // Logique métier
        if (deleted) {
            // Supprimé: 204 No Content, pas de corps
            return ResponseEntity.noContent().build();
        } else {
            // Non trouvé pour suppression: 404 Not Found
            return ResponseEntity.notFound().build();
        }
    }

    // Méthodes de service simulées
    private UserDto findUserById(Long id) { return id == 999 ? null : new UserDto(id, "user"+id, id+"@example.com"); }
    private UserDto saveUser(UserDto user) { user.setId(System.currentTimeMillis() % 1000); return user; }
    private boolean deleteUserById(Long id) { return id != 999; }
}

// Classe UserDto simulée
class UserDto {
    private Long id; private String username; private String email;
    public UserDto(Long id, String u, String e) {this.id=id; this.username=u; this.email=e;} public UserDto() {}
    public Long getId() {return id;} public void setId(Long id) {this.id=id;}
    public String getUsername() {return username;} public void setUsername(String u) {this.username=u;}
    public String getEmail() {return email;} public void setEmail(String e) {this.email=e;}
}

Ajouter des en-têtes personnalisés

ResponseEntity facilite également l'ajout d'en-têtes HTTP à votre réponse. Le builder retourné par les méthodes statiques (ok(), created(), status(), etc.) possède des méthodes pour cela :

  • header(String headerName, String... headerValues) : Ajoute un en-tête avec une ou plusieurs valeurs.
  • headers(HttpHeaders headers) : Ajoute plusieurs en-têtes en une fois via un objet HttpHeaders.

L'exemple le plus courant est l'ajout de l'en-tête Location lors de la création d'une ressource (statut `201 Created`), qui doit contenir l'URI de la ressource nouvellement créée.

@PostMapping("/with-headers")
public ResponseEntity createUserWithHeaders(@RequestBody UserDto newUser) {
    UserDto createdUser = saveUser(newUser);
    URI location = URI.create("/api/status-examples/" + createdUser.getId());

    // Construire la réponse avec statut 201, en-tête Location, et corps
    return ResponseEntity
            .created(location) // Définit le statut 201 et l'en-tête Location
            .header("X-Custom-Trace-Id", "trace-" + System.currentTimeMillis()) // Ajoute un en-tête perso
            .body(createdUser); // Définit le corps
}

Alternative simple : @ResponseStatus

Pour les cas où vous souhaitez simplement définir un code de statut de succès différent du `200 OK` par défaut, et que vous n'avez pas besoin de définir dynamiquement des en-têtes ou le statut en fonction de conditions complexes, vous pouvez utiliser l'annotation @ResponseStatus directement sur votre méthode de contrôleur.

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseStatus;
// ...
@PostMapping("/simple-create")
@ResponseStatus(HttpStatus.CREATED) // Définit le statut de succès à 201
public UserDto simpleCreateUser(@RequestBody UserDto newUser) {
    // La méthode retourne directement le corps
    // L'en-tête Location n'est pas défini ici
    return saveUser(newUser);
}

@DeleteMapping("/simple-delete/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT) // Définit le statut de succès à 204
public void simpleDeleteUser(@PathVariable Long id) {
    boolean deleted = deleteUserById(id);
    if (!deleted) {
        // Pour gérer le 404 ici, il faudrait lever une exception
        // throw new ResourceNotFoundException("User not found: " + id);
    }
    // Si succès, la méthode se termine et le statut 204 est envoyé
}

@ResponseStatus est plus simple pour ces cas spécifiques, mais ResponseEntity reste la solution la plus flexible et la plus puissante pour une gestion complète et conditionnelle des réponses HTTP dans vos API REST Spring Boot.