SwaggerUI is a really useful tool to display OpenAPI specifications as interactive API documentation. In order to generate SwaggerUI directly from a Spring Boot API Services application we need to do the following.
In the application’s pom.xml file include the following dependencies:
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>1.6.11</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-common</artifactId>
<version>1.6.11</version>
</dependency>
Where version can be substituted with the most recent one.
Create a Spring Bean to prepare the generic OpenAPI configuration. For example see below for the configuration of a test application:
@Bean
public OpenAPI openAPI() {
return new OpenAPI()
.info(new Info()
.title("Test API specs")
.summary("API of available services for the Test application.")
.description("API of available services for the Test application.")
.version(buildProperties.getVersion())
.license(new License().name("Apache 2.0")
.url("http://springdoc.org")))
.externalDocs(new ExternalDocumentation()
.description("Test API Documentation")
.url("https://testdocumentationurl.nodalpoint.com"));
}
If the API is behind reverse proxy and the proxy translates the URLs we need to do the following:
- Add X-Forwarded-Prefix header in proxy with the custom exposed name.
- Add the following propertied in the application.properties file
- server.forward-headers-strategy=framework – in order for the Spring Boot to handle X-Forwarded headers
- springdoc.api-docs.enabled=false – in order to disable the api-docs in PRD environments. Can be configured with spring profiles.
- springdoc.swagger-ui.enabled=false – in order to disable the swagger-ui in PRD environments. Can be configured with spring profiles.
- springdoc.swagger-ui.supportedSubmitMethods= – should be left blank in order ti disable the “Try It Out” action in Swagger UI. We can enable it based on the Http Methods we want i.e. post,put,get etc.
- Create a Spring Bean so the application can handle X-Forwarded header by registering a special Filter.
@Bean
ForwardedHeaderFilter forwardedHeaderFilter() {
return new ForwardedHeaderFilter();
}
Annotations
To add specific annotations and descriptions in each controller, the controllers need to be surrounded with the related information.
Some of the annotations that we can apply are the following:
Annotation | Position | Description |
---|---|---|
@Tag | class definition | Indicates the name and description of the swagger tag that will be used on the services of this class |
@Operation | controller method | Provides a description of the url |
@ApiResponses | controller method | Provide a list of @ApiResponse that the service returns |
@ApiResponse | inside @ApiResponses | A response code and description |
@Parameter | on each method parameter | Describes the specific parameter |
@ParameterObject | on each method parameter that is object type | Describes the specific parameter and breaks down its attributes. To be used also for the pageable parameter |
For example see below for service GET /internal/pub_reports of one of our applications:
@Operation(summary = "Get a list of publication reports")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "success"),
@ApiResponse(responseCode = "400", description = "Invalid request",
content = @Content),
@ApiResponse(responseCode = "401", description = "UNAUTHORIZED",
content = @Content),
@ApiResponse(responseCode = "403", description = "Forbidden",
content = @Content),
@ApiResponse(responseCode = "500", description = "Internal/Unhandled Exception",
content = @Content)
})
@GetMapping(path = "/internal/pub_reports", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Page<SearchReports>> getPublicationReports(
@Parameter(description = "A list of codes for the subject of the report",
array = @ArraySchema(schema = @Schema(type = "string", allowableValues = {"A", "B", "C"})))
@RequestParam(value = "subjectCode", required = false) Optional<List<String>> subjectCodes,
@Parameter(description = "A list of status codes for the report status",
array = @ArraySchema(schema = @Schema(type = "string", allowableValues = {"DIST", "OBSL", "SPRS", "UPLD"})))
@RequestParam(value = "reportStatusCode", required = false) Optional<List<String>> reportStatusCodes,
@Parameter(description = "A list of IDs for the ID of the participant of the report")
@RequestParam(value = "participantId", required = false) Optional<List<String>> marketParticipantIds,
@Parameter(description = "Report period start date - from value")
@RequestParam(value = "periodStartDateFrom", required = false) Optional<String> periodStartDateFrom,
@Parameter(description = "Report period start date - to value")
@RequestParam(value = "periodStartDateTo", required = false) Optional<String> periodStartDateTo,
@Parameter(description = "Report period end date - from value")
@RequestParam(value = "periodEndDateFrom", required = false) Optional<String> periodEndDateFrom,
@Parameter(description = "Report period end date - to value")
@RequestParam(value = "periodEndDateTo", required = false) Optional<String> periodEndDateTo,
@Parameter(description = "User that distributed the report")
@RequestParam(value = "distributedBy", required = false) Optional<String> distributedBy,
@Parameter(description = "Indicates if the report has been downloaded by the participant",
schema = @Schema(type = "boolean", allowableValues = {"false", "true"}))
@RequestParam(value = "isDownloaded", required = false) Optional<Boolean> isDownloaded,
Locale locale,
@ParameterObject
@PageableDefault(size = 200) @SortDefault(sort = "createdOn", direction = Sort.Direction.DESC) Pageable pageable,
HttpServletRequest httpServletRequest) {
// Check if user has been authenticated
Principal principal = httpServletRequest.getUserPrincipal();
if (principal == null) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
Page res = internalReportService.searchReports(subjectCodes,
reportStatusCodes,
marketParticipantIds,
periodStartDateFrom,
periodStartDateTo,
periodEndDateFrom,
periodEndDateTo,
distributedBy,
isDownloaded,
pageable);
return ResponseEntity.ok().body(res);
}
The above will create the following entry in Swagger (URL will be …/app-context/swagger-ui.html):
Note that in @Parameter, we can use multiple tags if required to better define the parameter. Use array = @ArraySchema when using a list parameter. Use schema = @Schema with allowableValues when need to define specific allowed values.
An example for @Tag, as added in the same application:
@RestController
@Slf4j
@Transactional
@RequestMapping("/internal/utils")
@Tag(name = "Internal Utils Resource", description = "Utils used from internal portal")
public class InternalUtilsResource {
...
Cheers!