Skip to main content

Reducing Swagger Annotation Overload

Context

The API documentation is an essential part of building REST APIs to make the services available to all audience. This documentation should help consumers of the service know which all services are available and its fine details. Also, there should be some simple way to test if the service is up.

SpringDoc simplifies the generation and maintenance of API docs based on the OpenAPI 3 specification for the spring boot applications. The exposed services are bound to change and the documentation needs to be updated as the services change. If this is done manually, then it will become a complex process, and it will be prone to error, especially as the number of REST services increase. This is where swagger helps to automate this documentation process and the consumers of this API would see the output of all of this in the swagger UI (for example, the Swagger API Doc Endpoint at /swagger/index.html).

The proliferation of Swagger annotation's means that there is a lot of duplicated APIResponse annotations that bloat the code and make it difficult to maintain and read.

Solution

To avoid the code duplication around swagger annotations, we have opted to use our own @interface Java annotation to "carry" these annotations and make them a reusable unit which will minimise the duplicated Swagger annotations from controller methods.

Implementation Examples

We have defined custom java annotations per CRUD operation to be used by the controller classes. This will promote re-usability of the swagger annotations rather than duplicating the code across multiple controller classes.

Example of java custom annotation:

In the below example we have:

  • Added multiple Swagger REST response annotations to our own annotation
  • Added the Security Requirement annotation

This is so that we have a single annotation that a developer can use to easily apply all of these Swagger repetitive annotations across multiple classes using just a single annotation per class, therefore fixing the problem being addressed - annotation bloat.

In the below example, we are creating ReadAPIResponses annotation.

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@ApiResponses({@ApiResponse(
responseCode = "404",
description = "Resource not found",
content = {@Content(
mediaType = "application/json",
schema = @Schema(
implementation = ErrorResponse.class))}),
@ApiResponse(
responseCode = "400",
description = "Bad Request",
content = {@Content(
mediaType = "application/json",
schema = @Schema(
implementation = ErrorResponse.class))}
)})
@SecurityRequirement(name = "bearerAuth")
public @interface ReadAPIResponses {}

Using the custom Annotation @ReadAPIResponses:

In the below code example, java custom annotation @ReadAPIResponses has been used.

@RestController
public class MenuController {

@GetMapping(value = "/{id}")
@Operation(tags = "Menu", summary = "Get a menu", description = "By passing the menu id, ...")
@ReadAPIResponses
ResponseEntity<MenuDTO> getMenu(
@PathVariable(name = "id") UUID id,
@Parameter(hidden = true) @RequestAttribute("CorrelationId") String correlationId) {

// Code here

}
}

Overriding the custom annotations:

We can override our new custom annotation entries by placing the annotation before the new custom annotation. In the below example, @ApiResponse entry will override the 200 response code in @ReadAPIResponses custom annotation as the @ApiResponse comes before @ReadAPIResponses. We just have to make that the annotations are placed in the right order.

@RestController
public class MenuController {

@GetMapping(value = "/{id}")
@Operation(tags = "Menu", summary = "Get a menu", description = "By passing the menu id, ...")
@ApiResponse(
responseCode = "200",
description = "Menu",
content =
@Content(
mediaType = MediaType.APPLICATION_JSON_VALUE,
schema = @Schema(implementation = MenuDTO.class)))
@ReadAPIResponses
ResponseEntity<MenuDTO> getMenu(
@PathVariable(name = "id") UUID id,
@Parameter(hidden = true) @RequestAttribute("CorrelationId") String correlationId) {

// Code here

}
}