OpenAPI et Swagger UI : documenter son API Spring Boot
L'idée en une phrase
OpenAPI est un format standard pour décrire une API REST : ses adresses, ce qu'elle attend, ce qu'elle renvoie, ses erreurs. Dans un projet Spring Boot, ce document peut être généré automatiquement depuis ton code et affiché dans une page web, Swagger UI, où l'on peut tester l'API directement.
Une image pour comprendre : la carte du restaurant
Dans un restaurant, le client ne va pas en cuisine pour savoir ce qu'il peut commander. Il lit la carte : le nom des plats, ce qu'ils contiennent, leur prix. La carte est un contrat entre la cuisine et la salle.
OpenAPI, c'est la carte de ton API :
- chaque plat est une adresse de l'API (
GET /produits/{id}) ; - la description dit ce qu'il faut fournir et ce qu'on reçoit ;
- la carte est écrite dans un format standard, que tout le monde sait lire : le développeur front-end, une autre équipe, un outil qui génère du code.
Et comme une carte, elle n'a de valeur que si elle est à jour : un plat retiré de la cuisine mais encore sur la carte, c'est un client mécontent.
Le vocabulaire : OpenAPI, Swagger, springdoc
Ces trois noms reviennent ensemble et sont souvent confondus :
| Nom | Ce que c'est |
|---|---|
| OpenAPI | le format de description, un standard ouvert (version 3.x) |
| Swagger | l'ancien nom du format (jusqu'à la version 2.0) ; aujourd'hui, le nom d'une famille d'outils |
| Swagger UI | une page web qui affiche un document OpenAPI de façon lisible, et permet de tester l'API |
| springdoc-openapi | une bibliothèque qui génère le document OpenAPI à partir de ton code Spring Boot |
Quand quelqu'un te dit « regarde le Swagger », il parle presque toujours de la page Swagger UI.
À quoi ressemble un document OpenAPI ?
C'est un fichier YAML ou JSON. Tu n'as pas besoin de l'écrire à la main, mais il faut savoir le lire :
openapi: 3.1.0
info:
title: API Boutique
version: 1.0.0
paths:
/produits/{id}:
get:
summary: Afficher un produit
parameters:
- name: id
in: path
required: true
schema:
type: integer
responses:
"200":
description: Le produit
content:
application/json:
schema:
$ref: "#/components/schemas/ProduitDto"
"404":
description: Produit introuvable
components:
schemas:
ProduitDto:
type: object
required: [nom, prix]
properties:
nom:
type: string
example: Clavier mécanique
prix:
type: number
example: 89.90
À lire comme ceci : « GET /produits/{id} attend un identifiant entier dans l'adresse. En cas de succès (200), elle renvoie un ProduitDto avec un nom et un prix, tous deux obligatoires. Si le produit n'existe pas, elle renvoie une 404. »
Le bloc components contient les schémas réutilisables, c'est-à-dire la forme des objets JSON. $ref y fait référence.
Générer la documentation avec Spring Boot
Une dépendance
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>...</version>
</dependency>
⚠️ La version de springdoc doit correspondre à celle de Spring Boot :
| Spring Boot | springdoc-openapi |
|---|---|
| 4.x | 3.x |
| 3.x | 2.x |
| 2.x | 1.x (ancien nom : springdoc-openapi-ui) |
Une version qui ne correspond pas donne une erreur au démarrage, ou une page Swagger UI introuvable.
Deux adresses
Lance l'application, puis ouvre :
| Adresse | Contenu |
|---|---|
http://localhost:8080/v3/api-docs |
le document OpenAPI, en JSON |
http://localhost:8080/swagger-ui/index.html |
la page Swagger UI |
Ce qui est généré sans rien écrire
springdoc lit ton code au démarrage et en déduit beaucoup de choses :
@RestController
@RequestMapping("/produits")
public class ProduitController {
@GetMapping("/{id}")
public ProduitDto afficher(@PathVariable Long id) { ... }
@PostMapping
public ResponseEntity<ProduitDto> creer(@Valid @RequestBody CreationProduitDto dto) { ... }
}
public record CreationProduitDto(
@NotBlank @Size(max = 100) String nom,
@NotNull @Positive BigDecimal prix) {}
| Dans ton code | Dans la documentation |
|---|---|
@GetMapping("/{id}") |
l'adresse et la méthode HTTP |
@PathVariable Long id |
un paramètre obligatoire, de type entier |
le record CreationProduitDto |
le schéma du corps de la requête |
@NotBlank, @NotNull |
le champ est marqué obligatoire |
@Size(max = 100) |
la longueur maximale |
C'est un vrai avantage : la documentation est construite à partir du code, donc elle suit les modifications. Et les règles de validation que tu écris pour Spring apparaissent aussi dans la documentation.
Améliorer la documentation avec des annotations
Le code ne dit pas tout : à quoi sert l'adresse, quelles erreurs elle peut renvoyer, un exemple de valeur. Quelques annotations complètent la carte :
@RestController
@RequestMapping("/produits")
@Tag(name = "Produits", description = "Consultation et gestion du catalogue")
public class ProduitController {
@Operation(summary = "Afficher un produit")
@ApiResponse(responseCode = "200", description = "Le produit")
@ApiResponse(responseCode = "404", description = "Aucun produit avec cet identifiant")
@GetMapping("/{id}")
public ProduitDto afficher(@PathVariable Long id) { ... }
}
public record ProduitDto(
@Schema(description = "Nom affiché dans le catalogue", example = "Clavier mécanique")
String nom,
@Schema(description = "Prix TTC en euros", example = "89.90")
BigDecimal prix) {}
| Annotation | Rôle |
|---|---|
@Tag |
regroupe les adresses par thème dans Swagger UI |
@Operation |
décrit en une phrase ce que fait l'adresse |
@ApiResponse |
documente un code de réponse possible, erreurs comprises |
@Schema |
décrit un champ et donne un exemple |
Astuce : n'annote pas tout par principe. Un
@Operation(summary = "Créer un produit")sur une méthodecreern'apprend rien. Concentre-toi sur ce que le code ne dit pas : les erreurs possibles, l'unité d'un montant, le format d'une date, un exemple réaliste.
Documenter les erreurs
C'est la partie la plus souvent oubliée, et la plus utile pour ceux qui appellent ton API. Si ton @RestControllerAdvice renvoie une 404 ou une 409 (voir la fiche sur les exceptions), la documentation doit le dire. Sinon, le développeur front-end découvre ces erreurs en production.
Tester avec Swagger UI
Dans Swagger UI, chaque adresse a un bouton Try it out. Tu remplis les paramètres et le corps de la requête, tu cliques sur Execute, et la page affiche :
- la requête réellement envoyée (avec la commande
curléquivalente) ; - la réponse : code HTTP, en-têtes, corps JSON.
C'est très pratique pour vérifier rapidement une adresse que tu viens d'écrire, sans outil supplémentaire.
Note : Swagger UI envoie de vraies requêtes à l'application. Sur ton poste, aucun risque. Sur un environnement partagé, une requête
DELETEsupprime vraiment des données.
Deux façons de travailler
| Approche | Le principe | Quand ? |
|---|---|---|
| Code first (le code d'abord) | on écrit les contrôleurs, la documentation est générée | le cas le plus courant, celui de cette fiche |
| Contract first (le contrat d'abord) | on écrit d'abord le document OpenAPI, puis on génère le code | quand plusieurs équipes doivent se mettre d'accord avant de développer |
Dans l'approche contract first, un outil comme openapi-generator lit le fichier YAML et génère les interfaces des contrôleurs, les DTO, ou un client prêt à l'emploi pour appeler l'API.
C'est aussi utile dans l'autre sens : si une autre équipe te fournit son document OpenAPI, tu peux générer le client Java pour appeler son API, au lieu d'écrire les DTO à la main. Si ton projet utilise ce générateur, ne modifie jamais le code généré : il serait écrasé à la prochaine génération.
La documentation est un contrat
Les applications qui appellent ton API (un front-end, une application mobile, une autre équipe) s'appuient sur ce contrat. Certaines modifications le cassent :
| Modification | Effet pour ceux qui appellent l'API |
|---|---|
| ajouter un champ dans une réponse | ✅ sans danger en général |
| ajouter une nouvelle adresse | ✅ sans danger |
renommer un champ (prix → montant) |
❌ leur code ne trouve plus le champ |
| supprimer un champ ou une adresse | ❌ leur code plante |
| rendre obligatoire un champ qui ne l'était pas | ❌ leurs requêtes sont refusées |
| changer le type d'un champ (nombre → texte) | ❌ leur lecture du JSON échoue |
Ce sont des modifications qui marchent parfaitement dans ton application et tes tests, mais qui cassent celle des autres.
Avant de renommer ou de supprimer quelque chose dans un DTO de réponse, demande qui utilise l'API.
Et en production ?
La page Swagger UI expose la liste complète de tes adresses et la forme de tes données. C'est précieux pour ceux qui développent, mais aussi pour quelqu'un qui chercherait une faille.
Dans beaucoup de projets, elle est désactivée en production, avec un profil Spring dédié :
# application-prod.properties
springdoc.api-docs.enabled=false
springdoc.swagger-ui.enabled=false
D'autres projets la gardent, mais protégée par une authentification. Le choix appartient à l'équipe ; l'important est que ce soit un choix, pas un oubli.
Les bonnes habitudes
- Garde la version de springdoc alignée sur celle de Spring Boot.
- Documente les erreurs que chaque adresse peut renvoyer, pas seulement le cas qui marche.
- Donne des exemples réalistes avec
@Schema(example = ...): un exemple vaut souvent mieux qu'une longue description. - Annote ce que le code ne dit pas, pas ce qu'il dit déjà.
- Utilise la validation (
@NotBlank,@Size…) : elle protège ton API et apparaît dans la documentation. - Ne renomme pas un champ de réponse sans prévenir : c'est une rupture du contrat.
- Désactive ou protège Swagger UI en production.
- Ne modifie jamais du code généré par openapi-generator.
À retenir
- OpenAPI est le format standard qui décrit une API REST ; Swagger UI est la page web qui l'affiche et permet de la tester.
- Avec springdoc-openapi, la documentation est générée depuis ton code, validation comprise.
- Quelques annotations (
@Tag,@Operation,@ApiResponse,@Schema) complètent ce que le code ne dit pas, en particulier les erreurs. - La documentation est un contrat : renommer ou supprimer un champ casse les applications qui appellent ton API.
- En production, Swagger UI est désactivée ou protégée.