Создание документации вручную - утомительный процесс. В этой статье мы рассмотрим основы Swagger и его возможности по документированию REST API в SpringBoot приложении.
Используемые версии
Java 17
Spring Boot 3.0.2
Swagger 2.2.8
SpringDoc 1.6.14 | 2.0.2
Изменения статьи
29.06.22: Обновил до Java 17. Обновил все зависимости до актуальных.
11.02.23: Обновил SpringBoot до 3.0.2. Обновил остальные зависимости. Добавил раздел с авторизацией в Swagger UI.
Что такое Swagger?
Swagger позволяет разработчикам описывать структуру своих API и генерировать интерактивную документацию, клиентские библиотеки и серверные модули для реализации API на разных языках.
Swagger предоставляет спецификацию для документирования REST API, которая называется OpenAPI Specification (OAS). Эта спецификация предоставляет четкий и лаконичный способ описания эндпойнтов, их параметров, моделей запросов и ответов и других аспектов API.
В данной статье рассматривается пример генерации OAS на основе уже существующих REST контроллеров. Мы разметим контроллеры и эндпойнты аннотациями, на основе которых будет сгенерирована OAS.
Существуют библиотеки, которые на основе OAS могут сгенерировать интерактивную документацию для API, которая позволит отправлять запросы, и получать ответы. Мы воспользуемся библиотекой SpringDoc.
Почему SpringDoc, а не SpringFox?
В интернете множество статей с использованием библиотеки SpringFox для генерации Swagger UI. Почему тогда я использую какой-то SpringDoc?
SpringFox был заброшен авторами. Последний коммит сделан в 2020 году, а количество issue уже перевалило за 200. В какой-то момент я столкнулся с каким-то багом в SpringFox, который никогда не будет исправлен, и стал искать альтернативы.
Такой альтернативой оказался SpringDoc, который активно развивается и поддерживает SpringBoot 3. Я успешно и быстро перевел свой проект с SpringFox на SpringDoc.
Также стоит упомянуть, что Swagger позволяет сгенерировать непосредственно код клиента или сервера по имеющейся OAS, для этого нужен генератор кода Swagger-Codegen.
Генератором я никогда не пользовался. Генерируемый код получается весьма неказистым и проще написать его самостоятельно.
Демо проект с REST API
Чтобы документировать API, для начала напишем его. Вы можете перейти к следующей главе, чтобы не тратить время.
Добавим примитивные контроллеры и одно DTO. Бизнес суть нашей системы – программа лояльности пользователей.
Для наших примеров достаточно слоя контроллеров, поэтому я позволю себе вольность опустить серверный и репозиторный слой и добавить бизнес логику непосредственно контроллер.
В качестве DTO у нас будет класс UserDto
– это пользователь нашей системы. У него пять полей, из которых 3 обязательны.
UserDto.java
public class UserDto {
private String key;
private String name;
private Long points = 0L;
private Gender gender;
private LocalDateTime regDate = LocalDateTime.now();
public UserDto() {
}
public UserDto(String key, String name, Gender gender) {
this.key = key;
this.name = name;
this.gender = gender;
}
public static UserDto of(String key, String value, Gender gender) {
return new UserDto(key, value, gender);
}
// getters and setters
}
public enum Gender {
MAN, WOMAN
}
Для взаимодействия с нашей бизнес-логикой, добавим три контроллера: UserController
, PointContoller
, SecretContoller
.
UserController
отвечает за добавление, обновление и получение пользователей.
UserController.java
@RestController
@RequestMapping("/api/user")
public class UserController {
private final Map<String, UserDto> repository;
public UserController(Map<String, UserDto> repository) {
this.repository = repository;
}
@PutMapping(produces = APPLICATION_JSON_VALUE)
public HttpStatus registerUser(@RequestBody UserDto userDto) {
repository.put(userDto.getKey(), userDto);
return HttpStatus.OK;
}
@PostMapping(produces = APPLICATION_JSON_VALUE)
public HttpStatus updateUser(@RequestBody UserDto userDto) {
if (!repository.containsKey(userDto.getKey())) return HttpStatus.NOT_FOUND;
repository.put(userDto.getKey(), userDto);
return HttpStatus.OK;
}
@GetMapping(value = "{key}", produces = APPLICATION_JSON_VALUE)
public ResponseEntity<UserDto> getSimpleDto(@PathVariable("key") String key) {
return ResponseEntity.ok(repository.get(key));
}
}
PointContoller
отвечает за взаимодействие с баллами пользователя. Один метод этого контроллера отвечает за добавление и удаление балов пользователям.
PointContoller.java
@RestController
@RequestMapping("api/user/point")
public class PointController {
private final Map<String, UserDto> repository;
public PointController(Map<String, UserDto> repository) {
this.repository = repository;
}
@PostMapping("{key}")
public HttpStatus changePoints(
@PathVariable String key,
@RequestParam("point") Long point,
@RequestParam("type") String type
) {
final UserDto userDto = repository.get(key);
userDto.setPoints(
"plus".equalsIgnoreCase(type)
? userDto.getPoints() + point
: userDto.getPoints() - point
);
return HttpStatus.OK;
}
}
Метод destroy
в SecretContoller
может удалить всех пользователей.
SecretContoller.java
@RestController
@RequestMapping("api/secret")
public class SecretController {
private final Map<String, UserDto> repository;
public SecretController(Map<String, UserDto> repository) {
this.repository = repository;
}
@GetMapping(value = "destroy")
public HttpStatus destroy() {
repository.clear();
return HttpStatus.OK;
}
}
Настраиваем Swagger
Теперь добавим Swagger в наш проект. Для этого добавьте следующие зависимости в pom.xml
:
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.0.2</version>
</dependency>
Для WebFlux используйте другую зависимость:
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webflux-ui</artifactId>
<version>2.0.2</version>
</dependency>
Данные зависимости подходят только для проектов на SpringBoot 3. Если вы используете SpringBoot 2, то вам необходимо добавить другие зависимости:
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-annotations</artifactId>
<version>2.2.8</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>1.6.14</version>
</dependency>
Документация SpringDoc для SpringBoot 2: https://springdoc.org
Swagger автоматически находит список всех контроллеров. При нажатии на любой из них будут перечислены допустимые методы HTTP (DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT).
Для каждого метода доступные следующие данные: статус ответа, тип содержимого и список параметров.
Поэтому после добавления зависимостей у нас уже есть документация, доступная по ссылке: http://localhost:8080/swagger-ui. А также есть OAS, доступный по адресу: http://localhost:8080/v3/api-docs.

Также можно вызвать каждый метод с помощью пользовательского интерфейса. Откроем метод добавления пользователей.

Пока у нас не очень информативная документация. Давайте исправим это. Для начала создадим класс конфигурации сваггера OpenApiConfig
- имя произвольное.
@OpenAPIDefinition(
info = @Info(
title = "Loyalty System Api",
description = "Loyalty System", version = "1.0.0",
contact = @Contact(
name = "Struchkov Mark",
email = "mark@struchkov.dev",
url = "https://mark.struchkov.dev"
)
)
)
public class OpenApiConfig {
}
title
– это название вашего приложения.version
– версия вашего API.contact
– ответственные за API.
Эти данные больше для визуальной красоты UI документации.
Разметка контроллеров
Переопределим описания контроллеров, чтобы сделать документацию понятнее. Для этого пометим контроллеры аннотацией @Tag
.
@Tag(name="Название контроллера", description="Описание контролера")
public class ControllerName {
// ... ... ... ... ...
}

Скрыть контроллер
У нас есть контроллер, который мы хотим скрыть – SecretController
. Аннотация @Hidden
поможет нам в этом.
@Hidden
@Tag(name = "Секретный контролер", description = "Позволяет удалить всех пользователей")
public class SecretController {
// ... ... ... ... ...
}
Наша документация стала намного понятнее, но давайте добавим описания для каждого метода контроллера.
Разметка методов
Аннотация @Operation
описывает возможности методов контроллера. Достаточно определить следующие значения:
summary
– короткое описание.description
– более полное описание.
@Operation(
summary = "Регистрация пользователя",
description = "Позволяет зарегистрировать пользователя"
)
public HttpStatus registerUser(@RequestBody UserDto userDto) {
// ... ... ... ... ...
}

Разметка переменных метода
При помощи аннотации Parameter
также опишем переменные в методе, который отвечает за управление баллами пользователей.
public HttpStatus changePoints(
@PathVariable @Parameter(description = "Идентификатор пользователя") String key,
@RequestParam("point") @Parameter(description = "Количество баллов") Long point,
@RequestParam("type") @Parameter(description = "Тип операции") TypeOperation type
) {
// ... ... ... ... ...
}
С помощью параметра required
можно задать обязательные поля для запроса. По умолчанию все поля необязательные.
Разметка DTO
Разработчики стараются называть переменные в классе понятными именами, но не всегда это помогает. Вы можете дать человеко-понятное описание самой DTO и ее переменным с помощью аннотации @Schema
.
@Schema(description = "Сущность пользователя")
public class UserDto {
@Schema(description = "Идентификатор")
private String key;
// ... ... ... ... ...
}
Сваггер заполнит переменные, формат которых он понимает: enum, даты. Но если некоторые поля DTO имеют специфичный формат, то помогите разработчикам добавив пример.
@Schema(description = "Идентификатор", example = "A-124523")
Выглядеть это будет так:


Но подождите, зачем мы передаем дату регистрации. Да и уникальный ключ чаще всего будет задаваться сервером. Скроем эти поля из swagger с помощью параметра Schema.AccessMode.READ_ONLY
:
public class UserDto {
@Schema(accessMode = Schema.AccessMode.READ_ONLY)
private String key;
// ... ... ... ... ...
}
Валидация
Про валидацию я подробно рассказывал в статье: "Валидация данных в SpringBoot". Здесь я лишь хочу показать, что валидация параметров методов контроллеров также отображается в Swagger.
Добавим валидацию в метод управления баллами пользователя в PointController
. Мы не хотим, чтобы можно было передать отрицательные баллы.
public HttpStatus changePoints(
// ... ... ... ... ...
@RequestParam("point") @Min(0) @Parameter(description = "Количество баллов") Long point,
// ... ... ... ... ...
) {
// ... ... ... ... ...
}
Давайте посмотрим на изменения спецификации.

Для поля point
появилось замечание minimum: 0
. И все это нам не стоило ни малейшего дополнительного усилия.
Авторизация в Swager
Если ваш API защищен авторизаций и аутентификаций, то вы не сможете просто так вызвать запрос из Swagger. Один из самых распространенных способов авторизации это JWT, о нем я рассказывал в отдельной статье.
Сейчас нам нужно объяснить сваггеру, какая авторизация у нас применяется и какие эндпойнты ей защищены.
Авторизация с использованием JWT
В первом случае рассмотрим старый добрый JWT. Swagger должен получить access-токен и добавлять его в Header запросов.
Начнем с добавления аннотации @SecurityScheme
над классом OpenApiConfig
:
@OpenAPIDefinition(...)
@SecurityScheme(
name = "JWT",
type = SecuritySchemeType.HTTP,
bearerFormat = "JWT",
scheme = "bearer"
)
public class OpenApiConfig {
}
Мы описали схему авторизации с использованием JWT. Теперь пометим аннотацией @SecurityRequirement
все эндпойнты, которые используют данный способ авторизации. В @SecurityRequirement
в атрибуте name
указываем название нашей схемы – JWT. Название должно совпадать с названием из аннотации @SecurityScheme
.
@PostMapping("{key}")
@SecurityRequirement(name = "JWT")
@Operation(summary = "Управление баллами", description = "Позволяет удалить или добавить баллы пользователю")
public HttpStatus changePoints(...) {
... ... ...
}
Запускаем приложение и открываем Swagger. Видим, что появилась кнопка Authorize. Нажмем на нее и получим возможность установить JWT токен для всех методов. Защищенные методы, которые мы пометили, будут отмечены значком замка.

Устанавливаем токен, после чего нажимаем кнопку Authorize. Переходим к защищенному эндпойнту и вызываем его.

Видим, что Swagger проставил заголовок Authorization
, а также сам добавил Bearer
к токену. То что нам и было нужно.
Авторизация с использованием Ouath2
С Oauth2 все оказалось сложнее. Проблема в том, что при авторизации с использованием Oauth2 SpringBoot генерирует JSESSIONID
куку, которую сохраняет в браузере. Дальше браузер передает эту куку с каждым запросом, что позволяет SpringBoot приложению понимать кто с ним общается.
Swagger же при Oauth2 авторизации генерирует себе access token, который пытается использовать при запросе. Проблема в том, что он никак не может сам получить значение JSESSIONID
, так как его генерирует Spring после успешной Oauth2 авторизации.
Если вы знаете, как решить эту проблему, пожалуйста напишите в комментариях. Я рабочего решения быстро найти не смог.
Поэтому для Ouath2 воспользуемся возможностью сваггера передавать куку.
@OpenAPIDefinition(...)
@SecurityScheme(
name = "jsessionid",
in = SecuritySchemeIn.COOKIE,
type = SecuritySchemeType.APIKEY,
paramName = "JSESSIONID"
)
public class OpenApiConfig {
}
Помечаем эндпойнты аннотацией:
@SecurityRequirement(name = "jsessionid")
Далее переходим по какому-нибудь урлу нашего API, видим Oauth2 окно авторизации. Проходим авторизацию. Теперь открываем консоль разработчика в браузере и находим раздел с куками.
Вместо настоящего Oauth2 сервера можно использовать mock-server. Который принимает любой email и любой пароль. Удобно использовать при локальной разработке. Конфигурацию подключения можно посмотреть в моей заметке.

Нас интересует кука JSESSIONID
, берем ее и вставляем в окно авторизации в Swagger.

Вот и все. Это будет работать.
Итог
Этих знаний вам хватит, чтобы сделать хорошее описание API вашего сервиса. Если нужны более тонкие настройки, то вы без труда сможете разобраться, открыв документацию к аннотациям сваггера.