Microservices API Documentation with Springdoc OpenAPI

Microservices API Documentation with Springdoc OpenAPI

I have already written about documentation for microservices more than two years ago in my article Microservices API Documentation with Swagger2. In that case, I used project SpringFox for auto-generating Swagger documentation for Spring Boot applications. Since that time the SpringFox library has not been actively developed by the maintainers – the latest version has been released in June 2018. Currently, the most important problems with this library are a lack of support for OpenAPI in the newest version 3, and for Spring reactive APIs built using WebFlux. All these features are implemented by Springdoc OpenAPI library. Therefore, it may threaten as a replacement for SpringFox as Swagger and OpenAPI 3 generation tool for Spring Boot applications.

Example

As a code example in this article we will use a typical microservices architecture built with Spring Cloud. It consists of Spring Cloud Config Server, Eureka discovery, and Spring Cloud Gateway as API gateway. We also have three microservices, which expose the REST API and are hidden behind the gateway for an external client. Each of them is exposing OpenAPI documentation that may be accessed on the gateway using Swagger UI. The repository with source code is available on GitHub: https://github.com/piomin/sample-spring-microservices-new.git. This repository has been used as an example in another article, so it contains code not only for Springdoc library demo. The following picture shows the architecture of our system.

microservices-api-documentation-springdoc-openapi.png

Implementating microservice with Springdoc OpenAPI

The first good news related to the Springdoc OpenAPI library is that it may exist together with the SpringFox library without any conflicts. This may simplify your migration into a new tool if anybody is using your Swagger documentation, for example for code generation of contract tests. To enable Springdoc for standard Spring MVC based application you need to include the following dependency into Maven pom.xml.

<dependency>
   <groupId>org.springdoc</groupId>
   <artifactId>springdoc-openapi-webmvc-core</artifactId>
   <version>1.2.32</version>
</dependency>

Each of our Spring Boot microservices is built on top of Spring MVC and provides endpoints for standard synchronous REST communication. However, the API gateway, which is built on top of Spring Cloud Gateway uses Netty as an embedded server and is based on reactive Spring WebFlux. It is also providing Swagger UI for accessing documentation exposed by all the microservices, so it must include a library that enables UI. The following two libraries must be included to enable Springdoc support for a reactive application based on Spring WebFlux.

<dependency>
   <groupId>org.springdoc</groupId>
   <artifactId>springdoc-openapi-webflux-core</artifactId>
   <version>1.2.31</version>
</dependency>
<dependency>
   <groupId>org.springdoc</groupId>
   <artifactId>springdoc-openapi-webflux-ui</artifactId>
   <version>1.2.31</version>
</dependency>

We can customize the default behavior of this library by setting properties in the Spring Boot configuration file or using @Beans. For example, we don’t want to generate OpenAPI manifests for all HTTP endpoints exposed by the application like Spring specific endpoints, so we may define a base package property for scanning as shown below. In our source code example each application YAML configuration file is located inside the config-service module.


springdoc:
  packagesToScan: pl.piomin.services.department

Here’s the main class of employee-service. We use @OpenAPIDefinition annotation to define a description for the application displayed on the Swagger site. As you see we can still have SpringFox enabled with @EnableSwagger2.

@SpringBootApplication
@EnableDiscoveryClient
@EnableSwagger2
@OpenAPIDefinition(info =
   @Info(title = "Employee API", version = "1.0", description = "Documentation Employee API v1.0")
)
public class EmployeeApplication {

   public static void main(String[] args) {
      SpringApplication.run(EmployeeApplication.class, args);
   }

}

OpenAPI on Spring Cloud Gateway

Once you start every microservice it will expose endpoint /v3/api-docs. We can customize that context by using property springdoc.api-docs.path in Spring configuration file. Since it is not required we may proceed to the implementation on the Spring Cloud Gateway. Springdoc doesn’t provide a similar class to SpringFox SwaggerResource, which has been used for exposing multiple APIs from different microservices in the previous article. Fortunately, there is a grouping mechanism that allows splitting OpenAPI definitions into different groups with a given name. To use it we need to declare a list of GroupOpenAPI beans.
Here’s the fragment of code inside gateway-service responsible for creating a list of OpenAPI resources handled by the gateway. First, we get all defined routes for services using RouteDefinitionLocator bean. Then we are fetching the id of each route and set it as a group name. As a result we have multiple OpenAPI resources under path /v3/api-docs/{SERVICE_NAME}, for example /v3/api-docs/employee.

@Autowired
RouteDefinitionLocator locator;

@Bean
public List<GroupedOpenApi> apis() {
   List<GroupedOpenApi> groups = new ArrayList<>();
   List<RouteDefinition> definitions = locator.getRouteDefinitions().collectList().block();
   definitions.stream().filter(routeDefinition -> routeDefinition.getId().matches(".*-service")).forEach(routeDefinition -> {
      String name = routeDefinition.getId().replaceAll("-service", "");
      GroupedOpenApi.builder().pathsToMatch("/" + name + "/**").setGroup(name).build();
   });
   return groups;
}

The API path like /v3/api-docs/{SERVICE_NAME} is not exactly what we want to achieve, because our routing to the downstream services is based on the service name fetched from discovery. So if you call address like http://localhost:8060/employee/** it is automatically load balanced between all registered instances of employee-service. Here’s the routes definition in gateway-service configuration.

spring:
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true
      routes:
      - id: employee-service
        uri: lb://employee-service
        predicates:
        - Path=/employee/**
        filters:
        - RewritePath=/employee/(?<path>.*), /$\{path}
      - id: department-service
        uri: lb://department-service
        predicates:
        - Path=/department/**
        filters:
        - RewritePath=/department/(?<path>.*), /$\{path}
      - id: organization-service
        uri: lb://organization-service
        predicates:
        - Path=/organization/**
        filters:
        - RewritePath=/organization/(?<path>.*), /$\{path}

Since Springdoc doesn’t allow us to customize the default behavior of the grouping mechanism to change the generated paths, we need to provide some workaround. My proposition is just to add a new route definition inside gateway configuration dedicated to Open API path handling. It rewrites path /v3/api-docs/{SERVICE_NAME} into /{SERVICE_NAME}/v3/api-docs, which is handled by the another routes responsible for interacting with Eureka discovery.

  - id: openapi
   uri: http://localhost:${server.port}
   predicates:
   - Path=/v3/api-docs/**
   filters:
   - RewritePath=/v3/api-docs/(?<path>.*), /$\{path}/v3/api-docs

Testing

To test our sample simple we need to run all microservice, config server, discovery and gateway. While microservices are available under a dynamically generated port, config server is available under 8888, discovery under 8061, and gateway under 8060. We can access each microservice by calling http://localhost:8060/{SERVICE_PATH}/**, for example http://localhost:8060/employee/**. The Swagger UI is available under address http://localhost:8060/swagger-ui.html. Before let’s take a look on Eureka after running all required Spring Boot applications.

microservice-api-documentation-with-springdoc-openapi

After accessing Swagger UI exposed on the gateway you may see that we can choose between all three microservices registered in the discovery. This is exactly what we wanted to achieve.

microservice-api-documentation-with-springdoc-openapi-ui

Conclusion

Springdoc OpenAPI is compatible with OpenAPI 3, and supports Spring WebFlux, while SpringFox is not. Therefore, it seems that the choice is obvious especially if you are using reactive APIs or Spring Cloud Gateway. In this article I demonstrated you how to use Springdoc in microservices architecture with a gateway pattern.

41 COMMENTS

comments user
Basant

Hi Piotr , i am following your blogs as it is and implemented same in my application but am getting error

http://localhost:8989/order/ (8989 is my api gateway port & order is my microservice)

when am trying this , am getting 404 , apart from that when i opened my swagger html

http://localhost:8989/swagger-ui.html

it is giving me below error

Failed to load API definition.

Errors

Fetch errorNot Found /v3/api-docs/order
Fetch errorNot Found /v3/api-docs/order

    comments user
    Piotr Mińkowski

    Hi. How did you set the port number?

comments user
Farooq

Hello Bro, Working with microservices and I was using rest doc for monolithic solutions. Is it possible to use spring rest doc also for micro services

    comments user
    Piotr Mińkowski

    Of course. This article is just showing it

comments user
Lucas Kurz

I think this is a great way of bundeling all API-Docs in one swagger. Makes handling with third-party-developers a lot easier.

Is it possible to refresh the Groups when i start new microservices? With this configuration I need to restart the gateway everytime…
I also changed that each service only exposes its API-DOCs and not the swagger-ui…

    comments user
    Piotr Mińkowski

    Hi. Yes, if you are adding a new application you need to restart the gateway. But in that case how would you like to add a new route definition without restart? You can do it using actuator endpoints for that – they allows to add a new route dynamically. If you prefer such solution you can annotate this bean List with @RefreshScope i force refreshing using also actuactor endpoint.

comments user
Lucas Kurz

With an eventlistener on RefreshRoutesEvent.class the gateway will refresh the swagger-ui when new services come up or go down.

Just reuse the apis()-Method and call a SwaggerUiConfigProperties.setSwaggerUrls(new HashSet()); before that.

Maybe there is an easier way to do this?

    comments user
    Piotr Mińkowski

    I think I have already answered in your previous question. If you are starting a new instance of application it is supported by default. If you are adding a new application you need to add a new route definition. That’s the case.

      comments user
      Lucas Kurz

      Well i forgot to say that I use the autodiscovery-mode for my services. But the EventListener I described here helped to solve my problem.

        comments user
        Piotr Mińkowski

        Ok, in that case you can provide the solution you have described or just annotate the bean with @RefreshBean

comments user
Manuel Waltschek

Hi, how did you get the top bar api spec selector/dropdown?
I’m using springdoc-openapi-webflux-ui version 1.3.9 on my gateway-service.
Do I need to use springfox + docket aswell? Also I do not really understand what List apis() is doing since it does not return anything…

Do I have to somehow configure swagger to include the api doc endpoints?
https://swagger.io/docs/open-source-tools/swagger-ui/usage/configuration/

    comments user
    Piotr Mińkowski

    Hi. Thanks for this comment. I took a look on it one more time, and you are right – I’m returning the empty list of GroupedOpenApi in apis() method – of course it is oversight, because I forgottten to call add method. But the truth is that it works even without this add, probably because of that line inside GroupedOpenApi constructor: SwaggerUiConfigProperties.addGroup(this.group). Interesting. I changed it in the code and call add method on the list, but of course it is not changing abything. Probably I could just call GroupedOpenApi.builder()…build() in the loop without returning anything and it will also work.

      comments user
      Norbert

      Hey Piotr,

      thanks for the article first of all. I can’t get it to work on my end though. Do you have the code somehwere publicly available? I feel like I’m missing a piece.

      As for me, the groups are not recognized (the dropdown is not showing up in the UI), although they’re being built up by the Bean method.
      A group shows up if I provide a single Bean of GroupedOpenAPI though.. I’m using the latest version of spring-open docs (1.3.9). Maybe that changed something.

      comments user
      Norbert

      It really is that constructor call. As for versions 1.3 they removed the addGroup call from the GroupedOpenAPI.
      This works then together with the rewriting:

      @Bean
      public void apis(RouteDefinitionLocator locator, SwaggerUiConfigProperties swaggerUiConfig) {
      List definitions = locator.getRouteDefinitions().collectList().block();
      definitions.stream()
      .filter(routeDefinition -> routeDefinition.getId().matches(“.*-service”))
      .forEach(routeDefinition -> {
      String name = routeDefinition.getId().replaceAll(“-service”, “”);
      swaggerUiConfig.addGroup(name);
      });
      }

      Cheers

        comments user
        Piotr Mińkowski

        Ok. Thanks

comments user
Hervé Guidetti

Hello,
Very interesting article.
I have a API gateway written with Zuul (verison 1). Do you think that we could implement a similar solution with Zuul ?
My API gateway doesn’t expose all of the micro-services end-points. Only part of it (the other ones are internal). Do you know a solution to filter the micro-services end-points in the documentation to match the ones exposed by the API Gateway ?

comments user
Ana

Hello, thank you for the article. Helped me a lot!
I do have one question/problem…

When I start the gateway and go to the swagger url everything works fine and I do see all swaggers from my routed services…however I can not call any of the service through this swagger becase it goes here: http://localhost:45003http,http://server.xyz/service/endpoint

One more question…how can i get api-docs to contain all services api-docs…now I only have failback endpoint in my gateway…did I not configure something correctly?

I am using the same versions as you in this example.

Thank you.

comments user
Ankit

How Would you handle if the “springdoc.api-docs.path” has been over-ridden by department microservice service to something like “department-application/api-document” , instead of simply using “v3/api-docs”

    comments user
    Piotr Mińkowski

    I would change that address on gateway from /v3/api-docs to another. I’m assuming you have the same path across all your microservices. If every microservice would use different path, the situation become more complicated. In that case you might use discovery server for that, where you would have to register the address of your Swagger endpoint.

comments user
Bikash

I have existing module using openapi below dependancies. Should i have to replace it with springfox-swagger2 and web-mvc core ?

org.springdoc
springdoc-openapi-ui
1.4.5

    comments user
    piotr.minkowski

    No, `springdoc-openapi-ui` works fine with Spring Boot.

comments user
Hugo Monteiro Vinhal

Hello! Is there any way I can add an authentication header input on the swagger UI?

    comments user
    piotr.minkowski

    Try to declare the OperationCustomizer bean:


    @Bean
    public OperationCustomizer customize() {
    return (operation, handlerMethod) -> operation.addParametersItem(
    new Parameter()
    .in("header")
    .required(true)
    .description("myCustomHeader")
    .name("myCustomHeader"));
    }

comments user
DK.Woo

Thanks for this good article.
But now I’m using springdoc-openapi-webflux-ui 1.6.8 for spring cloud gateway,
There is no ‘Select a definition’ select box shown for api groups.
Does anyone know about this?

comments user
Konrad Noskowicz

springdoc-openapi-webflux-core is dependency of springdoc-openapi-webflux-ui so only springdoc-openapi-webflux-ui is needed in pom.xml if both versions are same

    comments user
    piotr.minkowski

    ok. thanks for the tip

comments user
Maciek S

Web services are NOT microservices. that is the first thing about architectures.

    comments user
    piotr.minkowski

    My example apps are not web services. But beyond that, web service can also be a microservice

comments user
springlearner

When I hit any endpoint from Swagger UI, I get the below issue. Certainly something we need to add as a part of configuration

Failed to fetch.
Possible Reasons:

CORS
Network Failure
URL scheme must be “http” or “https” for CORS request.

comments user
Spring User

Hi, thank you for this nice blog post. I used it in its entirety however, I only received 401 response status for the “http://localhost:8070/v3/api-docs/main” (“main” is my micro-service name). My project implemented Reactive spring security, which is case for the most of the spinning application. I was able to eliminate the unauthorised response and I came here to share the code snippets. I hope it helps some one wandering because of spring security placed inside gateway service.

1. For the gateway routes
## OPEN-API SERVICE
– id: openapi
uri: http://localhost:${server.port}
predicates:
– Path=/v3/api-docs/**
## MAIN SERVICE – My actual micro-service
– id: main-service
uri: http://localhost:8081
predicates:
– Path=/main/**
filters:
– RewritePath=/main/(?.*), /$\{path}, AddRequestHeader=Authorization

2. For the swagger-ui (Place the code below in gateway’s yaml/properties file)
springdoc:
swagger-ui:
disable-swagger-default-url: true
urls:
– url: /main/v3/api-docs
name: Main-service REST API docs

3. For the Web or WebFlux security
http.authorizeExchange()
.pathMatchers(
“/main/v3/api-docs”,
“/v3/api-docs/**”,
“/v3/api-docs”,
“/swagger-resources/**”,
“/swagger-resources”,
“/configuration/ui”,
“/configuration/security”,
“/swagger-ui.html”,
“/swagger-ui/**”,
“/webjars/**”,
“/webjars”
)
.permitAll()

comments user
Jitu

Any idea how it will work with springboot3 : GroupedOpenApi is not working?

comments user
Jitu

Thank you. But for api gateway , the groupingApi is not working.

comments user
ilham

Hello, thanks for the information. Is it possible to get all definitions in one json?

    comments user
    piotr.minkowski

    Hello. As far as I know – no.

comments user
asali

Hi, thanks for the guide.
I am working with
springdoc-openapi v2.2.0
spring-cloud-starter-gateway with spring cloud version 2022.0.4
spring boot v3.1.2

I followed the guide but on Swagger UI still not able to load API definition for my other services.
Is there anything changed because of the newer version of springdoc-openapi?

    comments user
    piotr.minkowski

    No, there were no many changes there. In most cases, just a new artifacts to include.

Leave a Reply