Documenting RESTful APIs in Spring with Open API spec
Intro
Open API (previously Swagger) is world’s most widely used API description standard and it provides a formal specification for describing HTTP APIs.
This allows people to understand how and API works and how a sequence of API’s work together.
Specification is machine-readable so we can generate client code, create tests and much, much more…
Open API specification is widely used so enjoys support of wide range of vendors and tools.
History
Swagger came up with proposal for specification for describing RESTful APIs.
The specification creates a RESTful interface for easily developing and consuming an API by effectively mapping all the resources and operations associated with it.
Swagger spec became adopted by the industry and became industry standard know as OPEN API specification.
Spec had a couple of guidelines it was created by:
-
Spec should be written in JSON or YAML format.
-
It would be used to describe RESTful APIs
-
The goal is to keep the spec machine-readable
-
But also make it easy for humans to understand as well
Swagger also created Swagger-ui - popular web user interface for displaying and interacting with endpoints.
Key moments in history of the spec:
-
2010 - Swagger
-
2014 - Open API 2.0
-
2017 - Open API 3.0
-
2019 - springdoc-openapi
-
2021 - Open API 3.1
Spring doc - Open API
springdoc-openapi is library that allows automatic generation of open API documentation for your Spring projects.
It provides swagger user interface with a list of your endpoints and the ability to generate open API json spec.
Library is available as a dependency depending on whether you are using webMvc or webflux frameworks, and secondly if you need only open API spec generation, or swagger UI as well.
In our project case, where we use webMvc framework, we want the ability to generate both generate api spec and display swagger user interface, so we’ve included webmvc-ui starter.
<properties>
<springdoc.version>2.7.0</springdoc.version>
</properties>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<!--only open api spec, no swagger UI-->
<!--<artifactId>springdoc-openapi-starter-webmvc-api</artifactId>-->
<version>${springdoc.version}</version>
</dependency>
Out of the box integration
Out of the box Spring Doc - Open API library is integrated with the rest of the Spring ecosystem and with just inclusion of library as dependency on our project we’ll get out of the box rich functionality.
To demonstrate library features we’ve created a project that has following starters:
- webMvc
-
web application support, including REST, using Spring MVC
- actuator
-
built in (or custom) endpoints that let you monitor and manage your application - such as application health, metrics, sessions, etc.
- Spring Data JPA
-
persistence support with Java Persistence API
- validations
-
bean validation support
We can tell springdoc-openapi library to scan packages or include paths from our application in open API spec in two ways.
Either by specifying properties in our
:application.properties
# Packages to scan springdoc.packagesToScan=com.package1, com.package2
# Paths to include springdoc.pathsToMatch=/v1, /api/balance/**
or programmatically by defining
bean.GroupedOpenApi
@Bean
public GroupedOpenApi publicApi() {
return GroupedOpenApi.builder()
.group("springshop-public")
.pathsToMatch("/public/**")
.build();
}
Spring @RestControllers
We’ll get our api definition by just using normal Spring webMvc annotations and marking our package to be scanned by springdoc-openapi library.
For example class definition:
@RestController
@RequestMapping("/api/v1/email")
public class EmailApiController implements EmailApi {
@Override
@PostMapping
public ResponseEntity<EmailResponse> sendEmail(@RequestBody @Valid EmailRequest emailRequest) {
return ResponseEntity.ok(new EmailResponse("message-id", "Queued. Thank you."));
}
@Override
@GetMapping("/{id}")
public ResponseEntity<String> checkStatus(@PathVariable String id) {
return ResponseEntity.ok(id);
}
@GetMapping("/filter")
public Page<Object> filterEmails(Pageable pageable) {
throw new RuntimeException("TODO");
}
}
will be reflected in swagger ui:
Spring @RestControllerAdvice
Possible non success http responses are displayed automatically by using Springs
and annotating our exception handler methods with appropriate response statuses.@RestControllerAdvice
For example, exception handlers for BAD_REQUEST (400) and INTERNAL_SERVER_ERROR (500):
@ExceptionHandler(exception = BindException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
ResponseEntity<EmailApi.EmailError> processValidationException(WebRequest request) {
String bidingErrors = ((BindException) errorAttributes.getError(request))
.getBindingResult()
.getAllErrors()
.stream()
.map(oe -> (oe instanceof FieldError fe)
? fe.getField() + ": " + fe.getDefaultMessage()
: oe.getDefaultMessage()
)
.collect(Collectors.joining(", "));
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(new EmailApi.EmailError(bidingErrors));
}
@ExceptionHandler(RuntimeException.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
ResponseEntity<EmailApi.EmailError> processServerErrors(RuntimeException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(new EmailApi.EmailError(e.getLocalizedMessage()));
}
are picked up as possible responses in swagger ui api definition:
Actuator REST API
Actuator provides built in (or custom) endpoints that let you monitor and manage your application - such as application health, metrics, sessions, etc.
It comes with list of endpoints that are by default available under
/actuator root directory.
By changing properties in our application.properties configuration, we’ll allow actuator endpoints to be displayed in our app open API spec. We’ll show all endpoints actuator has to offer, and change default root actuator url from /actuator to /management.
springdoc.show-actuator=true management.endpoints.web.exposure.include=* management.endpoints.web.base-path=/management
In our spring app, open api spec is, by default, available on:
, and swagger ui on: /v3/api-docs
/swagger-ui/index.html
Spring Data JPA
Some of the Spring Data JPA framework build in objects, like
and Page
are automatically available in schema definition.Pageable
For method definition:
Page<Object> filterEmails(@ParameterObject Pageable pageable);
we’d get automatic example and schema description in swagger ui:
Validations
By including validation starter and validation annotation like
:@Size
record EmailRequest(@Email @NotBlank String to, @Size(min = 3, max = 100) String subject,@NotBlank String text) {}
restrictions are reflected in swagger ui schema fields definition:
Open API spec plugin
If we’d like open API spec (openapi.json) to be available as part of our build process, we can include maven plugin that will generate it during verify phase on the root of our target directory.
<executions>
<execution>
<goals>
<goal>start</goal>
<goal>stop</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-maven-plugin</artifactId>
<version>1.4</version>
<executions>
<execution>
<id>integration-test</id>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
</plugin>
Groups
Groups lets us aggregate API under common name by specifying endpoint paths to match or packages to scan.
In this example we group actuator endpoints under one, and our own endpoints under another group:
@Value("${springdoc.version}")
String springDocVersion;
@Bean
GroupedOpenApi actuatorApi(WebEndpointProperties endpointProperties) {
return GroupedOpenApi
.builder()
.addOpenApiCustomizer(apiCustomizer -> apiCustomizer.info(new Info().version(springDocVersion).description("Actuator API")))
.group("actuator")
.pathsToMatch(endpointProperties.getBasePath() + ALL_PATTERN)
.build();
}
@Bean
GroupedOpenApi emailApi() {
return GroupedOpenApi
.builder()
.addOpenApiCustomizer(apiCustomizer -> apiCustomizer.info(new Info().description("Email API")))
.group("email")
.packagesToScan("com.kanezi.springdoc_openapi_showcase")
.build();
}
We noted our app root package
to be used in packagesToScan method which means that our app every controller rest api definition will be under email group.com.kanezi.springdoc_openapi_showcase
Groups definitions are reflected in swagger ui group definition selection.
Customization
To customize default behaviour of springdoc-openapi library and configure how our endpoints are displayed we can change application properties or configure behaviour programmatically.
If our application only has REST API on i.e.
and does not have default web ui, we can display swagger by default on root url by setting property:/api/v1/…
# Opens swagger ui on base url springdoc.swagger-ui.use-root-path=true
REST API definition customization
By annotating our class and method definitions with springdoc-openapi annotations, i.e.
-
@Operation
-
@ApiResponse
-
@RequestBody
-
…
we can further customize how is our api definition described in open Api and swagger UI.
Especially useful is
content part where we can use @RequestBody
to provide valid json example for our users to immediately test our api.@ExampleObject
Example of method customization:
@Operation(summary = "sends email", tags = {"email"}
, description = """
sends email <strong>to</strong> and email address
<br>
with <strong>subject</strong>
<br>
and <strong>text</strong>
""")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "email successfully send",
content = @Content(schema = @Schema(implementation = EmailRequest.class)
, examples = @ExampleObject(value = """
{
"id": "message-id",
"message": "Queued. Thank you."
}
""")))
})
ResponseEntity<EmailResponse> sendEmail(
@RequestBody(
description = "email message",
content = @Content(examples = @ExampleObject(value = """
{
"to": "user@example.com",
"subject": "test mail",
"text": "Welcome to our site!"
}
""")))
EmailRequest emailRequest);