In this post, we’ll explore how to generate codes using the swagger-codegen from the defined OpenAPI file in a spring reactive application.
· What we need
· Overview
∘ What is API-First?
∘ Why API-First?
· OpenAPI Specification
· Spring Webflux Application
· Test the API
∘ Import OpenAPI definition in insomnia
∘ Test
· Conclusion
· References
Teams and organizations continue to embrace an API-first philosophy. — 2022 State of the API Report
In recent years, APIs have become the building blocks of software for companies and organizations. These companies use them to offer new services for their internal or external partners.
Therefore, it is necessary to provide service contracts, with documentation that constitutes an agreement between the differents stakeholders.
What we need
- Spring Boot 3
- Maven 3.8.+
- Java 17
- Postman or Insomnia
Overview
What is API-First?
API-first development is a development model in which applications are conceptualized and built by composing internal or external services delivered through APIs. This approach involves designing every API around a contract written in an API description language like OpenAPI.
Why API-First?
- Increasing developer productivity: Developers do not have to wait for updates to an API to be released before moving on to the next API. Teams can mock APIs and test API dependencies based on the established API definition.
- Reduces the risk of failure: API-First reduces the risk of failure by ensuring that APIs are reliable, consistent, and easy for developers to use.
- Reduces development cost: APIs specifications can be reused on many different projects. When a development team wants to build a new app, they don’t have to start from scratch which is time-consuming and costly. API-First design also allows most problems to be solved before any code is even written which helps prevent problems when it is time to integrate APIs with applications.
- Increases speed: API-First makes it possible to add new services and technologies to applications without having to re-architect the entire system. It allows businesses to optimize their speed to market by reusing existing software.
- Improve developer Experience: API-First ensures that developers have positive experiences using your APIs. Well-designed, well-documented, consistent APIs provide positive developer experiences because it’s easier to reuse code and onboard developers, and it reduces the learning curve.
OpenAPI Specification
The OpenAPI Specification (OAS) defines a standard, programming language-agnostic interface description for HTTP APIs, which allows both humans and computers to discover and understand the capabilities of a service without requiring access to source code, additional documentation, or inspection of network traffic.
The first thing to do is to define our API specification. In this story, we will create a library API, which will expose service contracts for CRUD operations.
An OpenAPI document that conforms to the OpenAPI Specification is itself a JSON object, which may be represented either in JSON or YAML format. For this story, we will use the YAML format.
Our YAML specifications file:
openapi: 3.0.3
info:
title: Library API - OpenAPI 3.0
description: |-
This is a sample Library Server based on the OpenAPI 3.0 specification. You can find out more about
Swagger at [https://swagger.io](https://swagger.io). In the third iteration of the Library, we've switched to the design first approach!
You can now help us improve the API whether it's by making changes to the definition itself or to the code.
That way, with time, we can improve the API in general, and expose some of the new features in OAS3.
Some useful links:
- [The library repository](https://github.com/anicetkeric/spring-reactive-openapi-codegen)
- [The source API definition for the library](https://github.com/anicetkeric/spring-reactive-openapi-codegen/blob/main/src/main/resources/openapi/yaml/library-openapi.yaml)
termsOfService: http://swagger.io/terms/
contact:
email: boottechnologies.ci@gmail.com
license:
name: Apache 2.0
url: http://www.apache.org/licenses/LICENSE-2.0.html
version: '1.0.11'
externalDocs:
description: Find out more about Swagger
url: http://swagger.io
servers:
- url: http://localhost:8080/api/v1
description: Development server
- url: http://localhost:8081/api/v1
description: Staging server
- url: http://localhost:8082/api/v1
description: Production server
tags:
- name: Book
description: Operations about book
externalDocs:
description: Find out more
url: http://swagger.io
- name: Author
description: Operations about author
externalDocs:
description: Find out more
url: http://swagger.io
paths:
/book:
get:
tags:
- Book
summary: Get all list book
operationId: getAllBook
responses:
'200':
description: return successfully
content:
application/json:
schema:
$ref: '#/components/schemas/SuccessResponse'
'400':
description: Bad Request
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'401':
description: You are not authorized to view the resource
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'403':
description: Accessing the resource you were trying to reach is forbidden
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'404':
description: The resource you were trying to reach is not found
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'405':
description: Method Not Allowed
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'500':
description: 'something went wrong! '
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
post:
tags:
- Book
summary: Create a new book.
description: Create new book in library
operationId: createBook
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Book'
required: true
responses:
'201':
description: Created successfully
content:
application/json:
schema:
$ref: '#/components/schemas/SuccessResponse'
'400':
description: 'something went wrong! '
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'401':
description: You are not authorized to view the resource
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'403':
description: Accessing the resource you were trying to reach is forbidden
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'404':
description: The resource you were trying to reach is not found
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'405':
description: Method Not Allowed
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'500':
description: Internal Server Error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/book/{id}:
get:
tags:
- Book
summary: get the "id" author.
description: Returns a single record
operationId: getOneBook
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
"500":
description: Internal Server Error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
"401":
description: Unauthorized
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
"405":
description: Method Not Allowed
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
"400":
description: Bad Request
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
"404":
description: entity by id not found
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
"200":
description: Successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/SuccessResponse'
put:
tags:
- Book
summary: Updates an existing book.
operationId: updateBook
parameters:
- name: id
in: path
required: true
schema:
type: string
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Book'
required: true
responses:
"500":
description: Internal Server Error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
"401":
description: Unauthorized
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
"405":
description: Validation exception
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
"400":
description: Invalid ID supplied
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
"404":
description: Contact not found
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
"200":
description: successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/SuccessResponse'
delete:
tags:
- Book
summary: Delete an existing book
operationId: deleteBook
parameters:
- name: id
in: path
required: true
schema:
type: string
description: book id
responses:
"500":
description: Internal Server Error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
"401":
description: Unauthorized
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
"405":
description: Validation exception
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
"400":
description: Invalid ID supplied
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
"404":
description: Contact not found
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
"200":
description: successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/SuccessResponse'
/author:
get:
tags:
- Author
summary: get all the authors.
operationId: getAllAuthor
responses:
'200':
description: return successfully
content:
application/json:
schema:
$ref: '#/components/schemas/SuccessResponse'
'400':
description: Bad Request
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'401':
description: You are not authorized to view the resource
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'403':
description: Accessing the resource you were trying to reach is forbidden
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'404':
description: The resource you were trying to reach is not found
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'405':
description: Method Not Allowed
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'500':
description: 'something went wrong! '
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
post:
tags:
- Author
summary: Create a new author.
description: Create new Author in library.
operationId: createAuthor
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Author'
required: true
responses:
201:
description: Created successfully
content:
application/json:
schema:
$ref: '#/components/schemas/SuccessResponse'
'400':
description: 'something went wrong! '
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'401':
description: You are not authorized to view the resource
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'403':
description: Accessing the resource you were trying to reach is forbidden
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'404':
description: The resource you were trying to reach is not found
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'405':
description: Method Not Allowed
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'500':
description: Internal Server Error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/author/{id}:
get:
tags:
- Author
summary: get the "id" author.
description: Returns a single record for Author
operationId: getOneAuthor
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
"500":
description: Internal Server Error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
"401":
description: Unauthorized
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
"405":
description: Method Not Allowed
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
"400":
description: Bad Request
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
"404":
description: entity by id not found
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
"200":
description: Successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/SuccessResponse'
put:
tags:
- Author
summary: Updates an existing author.
operationId: updateAuthor
parameters:
- name: id
in: path
required: true
schema:
type: string
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Author'
required: true
responses:
"500":
description: Internal Server Error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
"401":
description: Unauthorized
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
"405":
description: Validation exception
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
"400":
description: Invalid ID supplied
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
"404":
description: Contact not found
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
"200":
description: successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/SuccessResponse'
components:
schemas:
ErrorResponse:
type: object
properties:
code:
type: string
message:
type: string
description:
type: string
errors:
type: object
SuccessResponse:
type: object
properties:
data:
type: object
message:
type: string
Book:
required:
- title
- isbn
type: object
properties:
id:
type: string
title:
maxLength: 255
minLength: 1
type: string
description: book title
isbn:
maxLength: 13
minLength: 10
type: string
description: International Standard Book Number
page:
type: integer
description: number of page
description:
type: string
description: book summary
price:
type: number
format: float
description: book price
Author:
required:
- lastname
type: object
properties:
id:
type: string
firstname:
maxLength: 255
minLength: 1
type: string
description: firstname of author.
lastname:
maxLength: 255
minLength: 1
type: string
description: lastname of author.
The Swagger Editor offers an easy way to get started with the OpenAPI Specification (formerly known as Swagger) as well as the AsyncAPI specification, with support for Swagger 2.0, OpenAPI 3.0, and AsyncAPI 2.* versions.

The OpenAPI specification’s header contains some metadata about the API.
openapi: 3.0.3
info:
title: Library API - OpenAPI 3.0
description: |-
This is a sample Library Server based on the OpenAPI 3.0 specification. You can find out more about
Swagger at [https://swagger.io](https://swagger.io). In the third iteration of the Library, we've switched to the design first approach!
You can now help us improve the API whether it's by making changes to the definition itself or to the code.
That way, with time, we can improve the API in general, and expose some of the new features in OAS3.
Some useful links:
- [The library repository](https://github.com/anicetkeric/spring-reactive-openapi-codegen)
- [The source API definition for the library](https://github.com/anicetkeric/spring-reactive-openapi-codegen/blob/main/src/main/resources/openapi/yaml/library-openapi.yaml)
termsOfService: http://swagger.io/terms/
contact:
email: boottechnologies.ci@gmail.com
license:
name: Apache 2.0
url: http://www.apache.org/licenses/LICENSE-2.0.html
version: '1.0.11'
externalDocs:
description: Find out more about Swagger
url: http://swagger.io
servers:
- url: http://localhost:8080/api/v1
description: Development server
- url: http://localhost:8081/api/v1
description: Staging server
- url: http://localhost:8082/api/v1
description: Production server
tags:
- name: Book
description: Operations about book
externalDocs:
description: Find out more
url: http://swagger.io
- name: Author
description: Operations about author
externalDocs:
description: Find out more
url: http://swagger.io
Spring Webflux Application
Now that we have completed the OpenAPI specification, we can move on to using it in our project. Let’s start by creating a simple Spring Reactive project from start.spring.io, with the following dependencies: Spring Reactive Web and Lombok.

Add the OpenAPI specification to thesrc/main/resources/openapi/yaml directory with name library-openapi.yaml.
Then, we’ll be using the Open API Generator Maven plugin in the build section of the pom file.
<plugin>
<groupId>org.openapitools</groupId>
<artifactId>openapi-generator-maven-plugin</artifactId>
<!-- RELEASE_VERSION -->
<version>5.4.0</version>
<!-- /RELEASE_VERSION -->
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<inputSpec>${project.basedir}/src/main/resources/openapi/yaml/library-openapi.yaml</inputSpec>
<generatorName>spring</generatorName>
<packageName>${project.groupId}.springreactiveopenapicodegen</packageName>
<apiPackage>${project.groupId}.springreactiveopenapicodegen.api.v1</apiPackage>
<modelPackage>${project.groupId}.springreactiveopenapicodegen.dto</modelPackage>
<generateApiTests>false</generateApiTests>
<generateModelTests>false</generateModelTests>
<modelNameSuffix>DTO</modelNameSuffix>
<library>spring-boot</library>
<skipValidateSpec>false</skipValidateSpec>
<configOptions>
<reactive>true</reactive>
<delegatePattern>true</delegatePattern>
<interfaceOnly>true</interfaceOnly>
<dateLibrary>java8</dateLibrary>
<java8>true</java8>
<sourceFolder>src/main/java</sourceFolder>
<!--suppress UnresolvedMavenProperty -->
<additionalModelTypeAnnotations>@lombok.Builder @lombok.NoArgsConstructor @lombok.AllArgsConstructor</additionalModelTypeAnnotations>
<useTags>true</useTags>
</configOptions>
</configuration>
</execution>
</executions>
</plugin>
In order to fix compilation issues, we need to add additional dependencies to the pom.xml file:
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-annotations</artifactId>
<version>2.2.6</version>
</dependency>
<dependency>
<groupId>org.openapitools</groupId>
<artifactId>jackson-databind-nullable</artifactId>
<version>0.2.4</version>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
Run the command: mvn clean compile.The generator will generate the endpoint interfaces and DTO classes under the target folder.

For sample, the generated interface for the Book Controller API looks as follows:
@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2022-12-12T10:52:41.290045336+01:00[Europe/Madrid]")
@Validated
@Tag(name = "Book", description = "the Book API")
public interface BookApi {
/**
* POST /book : Create a new book.
* Create new book in library
*
* @param bookDTO (required)
* @return Created successfully (status code 201)
* or something went wrong! (status code 400)
* or You are not authorized to view the resource (status code 401)
* or Accessing the resource you were trying to reach is forbidden (status code 403)
* or The resource you were trying to reach is not found (status code 404)
* or Method Not Allowed (status code 405)
* or Internal Server Error (status code 500)
*/
@Operation(
operationId = "createBook",
summary = "Create a new book.",
tags = { "Book" },
responses = {
@ApiResponse(responseCode = "201", description = "Created successfully", content = @Content(mediaType = "application/json", schema = @Schema(implementation = SuccessResponseDTO.class))),
@ApiResponse(responseCode = "400", description = "something went wrong! ", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponseDTO.class))),
@ApiResponse(responseCode = "401", description = "You are not authorized to view the resource", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponseDTO.class))),
@ApiResponse(responseCode = "403", description = "Accessing the resource you were trying to reach is forbidden", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponseDTO.class))),
@ApiResponse(responseCode = "404", description = "The resource you were trying to reach is not found", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponseDTO.class))),
@ApiResponse(responseCode = "405", description = "Method Not Allowed", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponseDTO.class))),
@ApiResponse(responseCode = "500", description = "Internal Server Error", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponseDTO.class)))
}
)
@RequestMapping(
method = RequestMethod.POST,
value = "/book",
produces = { "application/json" },
consumes = { "application/json" }
)
default Mono<ResponseEntity<SuccessResponseDTO>> _createBook(
@Parameter(name = "BookDTO", description = "", required = true, schema = @Schema(description = "")) @Valid @RequestBody Mono<BookDTO> bookDTO,
@Parameter(hidden = true) final ServerWebExchange exchange
) {
return createBook(bookDTO, exchange);
}
// Override this method
default Mono<ResponseEntity<SuccessResponseDTO>> createBook(Mono<BookDTO> bookDTO, final ServerWebExchange exchange) {
Mono<Void> result = Mono.empty();
exchange.getResponse().setStatusCode(HttpStatus.NOT_IMPLEMENTED);
for (MediaType mediaType : exchange.getRequest().getHeaders().getAccept()) {
if (mediaType.isCompatibleWith(MediaType.valueOf("application/json"))) {
String exampleString = "{ \"data\" : \"{}\", \"message\" : \"message\" }";
result = ApiUtil.getExampleResponse(exchange, mediaType, exampleString);
break;
}
}
return result.then(Mono.empty());
}
/**
* DELETE /book/{id} : Delete an existing book
*
* @param id (required)
* @return Internal Server Error (status code 500)
* or Unauthorized (status code 401)
* or Validation exception (status code 405)
* or Invalid ID supplied (status code 400)
* or Contact not found (status code 404)
* or successful operation (status code 200)
*/
@Operation(
operationId = "deleteBook",
summary = "Delete an existing book",
tags = { "Book" },
responses = {
@ApiResponse(responseCode = "500", description = "Internal Server Error", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponseDTO.class))),
@ApiResponse(responseCode = "401", description = "Unauthorized", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponseDTO.class))),
@ApiResponse(responseCode = "405", description = "Validation exception", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponseDTO.class))),
@ApiResponse(responseCode = "400", description = "Invalid ID supplied", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponseDTO.class))),
@ApiResponse(responseCode = "404", description = "Contact not found", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponseDTO.class))),
@ApiResponse(responseCode = "200", description = "successful operation", content = @Content(mediaType = "application/json", schema = @Schema(implementation = SuccessResponseDTO.class)))
}
)
@RequestMapping(
method = RequestMethod.DELETE,
value = "/book/{id}",
produces = { "application/json" }
)
default Mono<ResponseEntity<SuccessResponseDTO>> _deleteBook(
@Parameter(name = "id", description = "", required = true, schema = @Schema(description = "")) @PathVariable("id") String id,
@Parameter(hidden = true) final ServerWebExchange exchange
) {
return deleteBook(id, exchange);
}
// Override this method
default Mono<ResponseEntity<SuccessResponseDTO>> deleteBook(String id, final ServerWebExchange exchange) {
Mono<Void> result = Mono.empty();
exchange.getResponse().setStatusCode(HttpStatus.NOT_IMPLEMENTED);
for (MediaType mediaType : exchange.getRequest().getHeaders().getAccept()) {
if (mediaType.isCompatibleWith(MediaType.valueOf("application/json"))) {
String exampleString = "{ \"data\" : \"{}\", \"message\" : \"message\" }";
result = ApiUtil.getExampleResponse(exchange, mediaType, exampleString);
break;
}
}
return result.then(Mono.empty());
}
/**
* GET /book : Get all list book
*
* @return return successfully (status code 200)
* or Bad Request (status code 400)
* or You are not authorized to view the resource (status code 401)
* or Accessing the resource you were trying to reach is forbidden (status code 403)
* or The resource you were trying to reach is not found (status code 404)
* or Method Not Allowed (status code 405)
* or something went wrong! (status code 500)
*/
@Operation(
operationId = "getAllBook",
summary = "Get all list book",
tags = { "Book" },
responses = {
@ApiResponse(responseCode = "200", description = "return successfully", content = @Content(mediaType = "application/json", schema = @Schema(implementation = SuccessResponseDTO.class))),
@ApiResponse(responseCode = "400", description = "Bad Request", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponseDTO.class))),
@ApiResponse(responseCode = "401", description = "You are not authorized to view the resource", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponseDTO.class))),
@ApiResponse(responseCode = "403", description = "Accessing the resource you were trying to reach is forbidden", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponseDTO.class))),
@ApiResponse(responseCode = "404", description = "The resource you were trying to reach is not found", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponseDTO.class))),
@ApiResponse(responseCode = "405", description = "Method Not Allowed", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponseDTO.class))),
@ApiResponse(responseCode = "500", description = "something went wrong! ", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponseDTO.class)))
}
)
@RequestMapping(
method = RequestMethod.GET,
value = "/book",
produces = { "application/json" }
)
default Mono<ResponseEntity<SuccessResponseDTO>> _getAllBook(
@Parameter(hidden = true) final ServerWebExchange exchange
) {
return getAllBook(exchange);
}
// Override this method
default Mono<ResponseEntity<SuccessResponseDTO>> getAllBook( final ServerWebExchange exchange) {
Mono<Void> result = Mono.empty();
exchange.getResponse().setStatusCode(HttpStatus.NOT_IMPLEMENTED);
for (MediaType mediaType : exchange.getRequest().getHeaders().getAccept()) {
if (mediaType.isCompatibleWith(MediaType.valueOf("application/json"))) {
String exampleString = "{ \"data\" : \"{}\", \"message\" : \"message\" }";
result = ApiUtil.getExampleResponse(exchange, mediaType, exampleString);
break;
}
}
return result.then(Mono.empty());
}
/**
* GET /book/{id} : get the \"id\" author.
* Returns a single record
*
* @param id (required)
* @return Internal Server Error (status code 500)
* or Unauthorized (status code 401)
* or Method Not Allowed (status code 405)
* or Bad Request (status code 400)
* or entity by id not found (status code 404)
* or Successful operation (status code 200)
*/
@Operation(
operationId = "getOneBook",
summary = "get the \"id\" author.",
tags = { "Book" },
responses = {
@ApiResponse(responseCode = "500", description = "Internal Server Error", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponseDTO.class))),
@ApiResponse(responseCode = "401", description = "Unauthorized", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponseDTO.class))),
@ApiResponse(responseCode = "405", description = "Method Not Allowed", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponseDTO.class))),
@ApiResponse(responseCode = "400", description = "Bad Request", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponseDTO.class))),
@ApiResponse(responseCode = "404", description = "entity by id not found", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponseDTO.class))),
@ApiResponse(responseCode = "200", description = "Successful operation", content = @Content(mediaType = "application/json", schema = @Schema(implementation = SuccessResponseDTO.class)))
}
)
@RequestMapping(
method = RequestMethod.GET,
value = "/book/{id}",
produces = { "application/json" }
)
default Mono<ResponseEntity<SuccessResponseDTO>> _getOneBook(
@Parameter(name = "id", description = "", required = true, schema = @Schema(description = "")) @PathVariable("id") String id,
@Parameter(hidden = true) final ServerWebExchange exchange
) {
return getOneBook(id, exchange);
}
// Override this method
default Mono<ResponseEntity<SuccessResponseDTO>> getOneBook(String id, final ServerWebExchange exchange) {
Mono<Void> result = Mono.empty();
exchange.getResponse().setStatusCode(HttpStatus.NOT_IMPLEMENTED);
for (MediaType mediaType : exchange.getRequest().getHeaders().getAccept()) {
if (mediaType.isCompatibleWith(MediaType.valueOf("application/json"))) {
String exampleString = "{ \"data\" : \"{}\", \"message\" : \"message\" }";
result = ApiUtil.getExampleResponse(exchange, mediaType, exampleString);
break;
}
}
return result.then(Mono.empty());
}
/**
* PUT /book/{id} : Updates an existing book.
*
* @param id (required)
* @param bookDTO (required)
* @return Internal Server Error (status code 500)
* or Unauthorized (status code 401)
* or Validation exception (status code 405)
* or Invalid ID supplied (status code 400)
* or Contact not found (status code 404)
* or successful operation (status code 200)
*/
@Operation(
operationId = "updateBook",
summary = "Updates an existing book.",
tags = { "Book" },
responses = {
@ApiResponse(responseCode = "500", description = "Internal Server Error", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponseDTO.class))),
@ApiResponse(responseCode = "401", description = "Unauthorized", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponseDTO.class))),
@ApiResponse(responseCode = "405", description = "Validation exception", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponseDTO.class))),
@ApiResponse(responseCode = "400", description = "Invalid ID supplied", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponseDTO.class))),
@ApiResponse(responseCode = "404", description = "Contact not found", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponseDTO.class))),
@ApiResponse(responseCode = "200", description = "successful operation", content = @Content(mediaType = "application/json", schema = @Schema(implementation = SuccessResponseDTO.class)))
}
)
@RequestMapping(
method = RequestMethod.PUT,
value = "/book/{id}",
produces = { "application/json" },
consumes = { "application/json" }
)
default Mono<ResponseEntity<SuccessResponseDTO>> _updateBook(
@Parameter(name = "id", description = "", required = true, schema = @Schema(description = "")) @PathVariable("id") String id,
@Parameter(name = "BookDTO", description = "", required = true, schema = @Schema(description = "")) @Valid @RequestBody Mono<BookDTO> bookDTO,
@Parameter(hidden = true) final ServerWebExchange exchange
) {
return updateBook(id, bookDTO, exchange);
}
// Override this method
default Mono<ResponseEntity<SuccessResponseDTO>> updateBook(String id, Mono<BookDTO> bookDTO, final ServerWebExchange exchange) {
Mono<Void> result = Mono.empty();
exchange.getResponse().setStatusCode(HttpStatus.NOT_IMPLEMENTED);
for (MediaType mediaType : exchange.getRequest().getHeaders().getAccept()) {
if (mediaType.isCompatibleWith(MediaType.valueOf("application/json"))) {
String exampleString = "{ \"data\" : \"{}\", \"message\" : \"message\" }";
result = ApiUtil.getExampleResponse(exchange, mediaType, exampleString);
break;
}
}
return result.then(Mono.empty());
}
}
We can implement the BookApi interfaces created in our project like this:
/**
* REST controller for managing {@link com.bootlabs.springreactiveopenapicodegen.dto.BookDTO}.
*/
@Validated
@Tag(name = "Book", description = "Book controller")
@RestController
@RequestMapping("/api/v1")
public class BookController implements BookApi {
/**
* {@inheritDoc}
*/
@Override
public Mono<ResponseEntity<SuccessResponseDTO>> _createBook(
@Parameter(name = "BookDTO", description = "", required = true, schema = @Schema(description = "")) @Valid @RequestBody Mono<BookDTO> bookDTO,
@Parameter(hidden = true) final ServerWebExchange exchange
) {
return bookDTO
.map(b -> ResponseEntity.created(URI.create("/api/v1/book/" + b.getId()))
.body(new SuccessResponseDTO(b, "book created Successfully")));
}
/**
* {@inheritDoc}
*/
@Override
public Mono<ResponseEntity<SuccessResponseDTO>> _deleteBook(
@Parameter(name = "id", description = "", required = true, schema = @Schema(description = "")) @PathVariable("id") String id,
@Parameter(hidden = true) final ServerWebExchange exchange
) {
return Mono.just(ResponseEntity
.ok()
.body(new SuccessResponseDTO(null, "Book deleted Successfully")));
}
/**
* {@inheritDoc}
*/
@Override
public Mono<ResponseEntity<SuccessResponseDTO>> _getAllBook(
@Parameter(hidden = true) final ServerWebExchange exchange
) {
return Mono.just(ResponseEntity
.ok()
.body(new SuccessResponseDTO(Collections.emptyList(), "result found")));
}
/**
* {@inheritDoc}
*/
@Override
public Mono<ResponseEntity<SuccessResponseDTO>> _getOneBook(
@Parameter(name = "id", description = "", required = true, schema = @Schema(description = "")) @PathVariable("id") String id,
@Parameter(hidden = true) final ServerWebExchange exchange
) {
return Mono.just(ResponseEntity
.ok()
.body(new SuccessResponseDTO(BookDTO.builder().id(id).build(), "result found")));
}
/**
* {@inheritDoc}
*/
@Override
public Mono<ResponseEntity<SuccessResponseDTO>> _updateBook(
@Parameter(name = "id", description = "", required = true, schema = @Schema(description = "")) @PathVariable("id") String id,
@Parameter(name = "BookDTO", description = "", required = true, schema = @Schema(description = "")) @Valid @RequestBody Mono<BookDTO> bookDTO,
@Parameter(hidden = true) final ServerWebExchange exchange
) {
return updateBook(id, bookDTO, exchange);
}
}
This implementation of the controller class is shortened to simply return static dummy data. We notice that our class BookController implements all the methods of the interface generated BookApi.
Test the API
Import OpenAPI definition in insomnia
Import data from a file > Select OpenAPI file > New > Request Collection



Test
Now we can run our application and test it.


Done. It works fine. 🙂
Conclusion
In this story, we learned how to use the OpenAPI specification in a spring Webflux project. At this point, the development team can start implementing the business logic.
The complete source code is available on GitHub.
References
- https://www.postman.com/state-of-api/
- https://spec.openapis.org/oas/latest.html
- https://openapi-generator.tech/docs/generators/spring/
- https://www.postman.com/api-first/
- https://swagger.io/resources/articles/adopting-an-api-first-approach/
- https://github.com/swagger-api/swagger-codegen/tree/master/modules/swagger-codegen-maven-plugin
- https://github.com/OpenAPITools/openapi-generator
