How can a Spring Cloud Gateway expose its routes through a Swagger UI? Here's a minimalistic static Gateway:
package com.example.gatewaydemo.routing;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
@Configuration
public class MyRoutingConfig {
@Bean
public RouteLocator routeLocator(RouteLocatorBuilder routeLocatorBuilder) {
return routeLocatorBuilder.routes()
.route(predicateSpec -> predicateSpec
.path("/uuid")
.and()
.method(HttpMethod.GET)
.uri("https://httpbin.org")
).build();
}
}
In a regular Spring web app, adding Swagger dependencies is enough
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>3.0.0</version>
</dependency>
It's not the case with Gateway
My Gateway uses Spring Boot 3 and Java 17 so I included this dependency instead
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.0.2</version>
</dependency>
However, it's clear I have to provide some configuration because if I simply include that springdoc dependency and include the @OpenApiDefinition annotation
package by.afinny.apigateway;
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.info.Info;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@OpenAPIDefinition(info = @Info(title = "API Gateway", version = "1.0", description = "Documentation API Gateway v1.0"))
public class ApiGatewayV2Application {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayV2Application.class, args);
}
}
I get this response when making a request to /swagger-ui.html (which gets redirected to /webjars/swagger-ui/index.html). Sorry, it's ui after all so I have to include a screenshot
Failed to load remote configuration
Please note that I retrieve services and their docs dynamically so static solutions like
springdoc:
enable-native-support: true
api-docs:
enabled: true
swagger-ui:
enabled: true
path: /swagger-ui.html
config-url: /v3/api-docs/swagger-config
urls:
- url: /v3/api-docs
name: API Gateway Service
primaryName: API Gateway Service
- url: /product-service/v3/api-docs
name: Product Service
primaryName: Product Service
- url: /price-service/v3/api-docs
name: Price Service
primaryName: Price Service
won't fit. That is, at startup my Gateway doesn't know whether there are product-service or price-service running
In a sense, I guess, my question boils down to this: how do I write Java config for Swagger UI in a Spring Cloud Gateway application?
UPD
Here's what I tried
springdoc:
api-docs:
enabled: false
swagger-ui:
enabled: true
path: /swagger-ui.html
config-url: /swagger-ui-config
package by.afinny.apigateway.controller;
import by.afinny.apigateway.model.uiConfig.SwaggerUiConfig;
import by.afinny.apigateway.service.SwaggerUiConfigProvider;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
@RestController
@RequiredArgsConstructor
public class SwaggerUiConfigController {
private final SwaggerUiConfigProvider configProvider;
@GetMapping("/swagger-ui-config")
public Mono<SwaggerUiConfig> getConfig() {
return configProvider.getSwaggerUiConfig();
}
}
package by.afinny.apigateway.model.uiConfig;
import by.afinny.apigateway.model.documentedApplication.SwaggerApplication;
import by.afinny.apigateway.service.SwaggerApplicationSerializer;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import lombok.Getter;
import lombok.NoArgsConstructor;
import java.util.Collection;
@NoArgsConstructor
@Getter
public class SwaggerUiConfig {
@JsonProperty("urls")
@JsonSerialize(contentUsing = SwaggerApplicationSerializer.class) // because SwaggerApplication contains a ton of other properties
private Collection<SwaggerApplication> swaggerApplications;
public SwaggerUiConfig(Collection<SwaggerApplication> swaggerApplications) {
this.swaggerApplications = swaggerApplications;
}
public static SwaggerUiConfig from(Collection<SwaggerApplication> swaggerApplications) {
return new SwaggerUiConfig(swaggerApplications);
}
}
package by.afinny.apigateway.service;
import by.afinny.apigateway.model.documentedApplication.SwaggerApplication;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import lombok.SneakyThrows;
import java.io.IOException;
public class SwaggerApplicationSerializer extends JsonSerializer<SwaggerApplication> {
@Override
@SneakyThrows
public void serialize(SwaggerApplication swaggerApplication, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeStartObject();
jsonGenerator.writeStringField("url", swaggerApplication.getUrl());
jsonGenerator.writeStringField("name", swaggerApplication.getName());
jsonGenerator.writeEndObject();
}
}
The returned JSON looks correct
{
"urls": [
{
"url": "/HELLOWORLD/v3/api-docs",
"name": "HELLOWORLD"
}
]
}
However, I still get the same redirect and the same "failed to load configuration" message, nothing changed. What is my mistake?


Your Gateway should serve the docs too
I won't include the entire code, but here's my
SwaggerUiSupport.EndpointCollectoris an interface that represent an endpoint cache (all endpoints collected from Eureka applications, assuming they have a Swagger/OpenApi/Springdoc dependency)