Search code examples
javaspring-bootswagger-ui

Swagger UI put my every endpoint in the "default" group. Why is it doing it?


Swagger UI put my every endpoint in the "default" group

Here's the JSON it gets (don't ask how I got it). Notice there are no tags (I also tried empty lists instead of nulls which are not serialized by Jackson)

{
  "openapi": "3.0.3",
  "info": {
    "title": "Api Documentation",
    "description": "Api Documentation",
    "termsOfService": "urn:tos",
    "contact": {    },
    "license": {
      "name": "Apache 2.0",
      "url": "http://www.apache.org/licenses/LICENSE-2.0"
    },
    "version": "1.0"
  },
  "servers": [
    {
      "url": "https://localhost:8080",
      "description": "Api-Gateway-V2"
    }
  ],
  "paths": {
    "/api/v1/hello-world": {
      "get": {
        "summary": "getHelloWorld",
        "operationId": "getHelloWorldUsingGET",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessMessage",
                  "exampleSetFlag": false
                },
                "exampleSetFlag": false
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Not Found"
          }
        }
      }
    },
    "/api/v1/joy": {
      "get": {
        "summary": "getMessageOfJoy",
        "operationId": "getMessageOfJoyUsingGET",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessMessage",
                  "exampleSetFlag": false
                },
                "exampleSetFlag": false
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Not Found"
          }
        }
      }
    },
    "/api/v1/error": {
      "get": {
        "summary": "error",
        "operationId": "errorUsingGET",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/FailureMessage",
                  "exampleSetFlag": false
                },
                "exampleSetFlag": false
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Not Found"
          }
        }
      },
      "put": {
        "summary": "error",
        "operationId": "errorUsingPUT",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/FailureMessage",
                  "exampleSetFlag": false
                },
                "exampleSetFlag": false
              }
            }
          },
          "201": {
            "description": "Created"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Not Found"
          }
        }
      },
      "post": {
        "summary": "error",
        "operationId": "errorUsingPOST",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/FailureMessage",
                  "exampleSetFlag": false
                },
                "exampleSetFlag": false
              }
            }
          },
          "201": {
            "description": "Created"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Not Found"
          }
        }
      },
      "delete": {
        "summary": "error",
        "operationId": "errorUsingDELETE",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/FailureMessage",
                  "exampleSetFlag": false
                },
                "exampleSetFlag": false
              }
            }
          },
          "204": {
            "description": "No Content"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          }
        }
      },
      "options": {
        "summary": "error",
        "operationId": "errorUsingOPTIONS",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/FailureMessage",
                  "exampleSetFlag": false
                },
                "exampleSetFlag": false
              }
            }
          },
          "204": {
            "description": "No Content"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          }
        }
      },
      "head": {
        "summary": "error",
        "operationId": "errorUsingHEAD",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/FailureMessage",
                  "exampleSetFlag": false
                },
                "exampleSetFlag": false
              }
            }
          },
          "204": {
            "description": "No Content"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          }
        }
      },
      "patch": {
        "summary": "error",
        "operationId": "errorUsingPATCH",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/FailureMessage",
                  "exampleSetFlag": false
                },
                "exampleSetFlag": false
              }
            }
          },
          "204": {
            "description": "No Content"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          }
        }
      },
      "trace": {
        "summary": "error",
        "operationId": "errorUsingTRACE",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/FailureMessage",
                  "exampleSetFlag": false
                },
                "exampleSetFlag": false
              }
            }
          },
          "204": {
            "description": "No Content"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "FailureMessage": {
        "title": "FailureMessage",
        "type": "object",
        "properties": {
          "message": {
            "type": "string",
            "exampleSetFlag": false,
            "types": [
              "string"
            ]
          },
          "method": {
            "type": "string",
            "exampleSetFlag": false,
            "types": [
              "string"
            ],
            "enum": [
              "DELETE",
              "GET",
              "HEAD",
              "OPTIONS",
              "PATCH",
              "POST",
              "PUT",
              "TRACE"
            ]
          },
          "request_path": {
            "type": "string",
            "exampleSetFlag": false,
            "types": [
              "string"
            ]
          }
        },
        "exampleSetFlag": false,
        "types": [
          "object"
        ]
      },
      "SuccessMessage": {
        "title": "SuccessMessage",
        "type": "object",
        "properties": {
          "message": {
            "type": "string",
            "exampleSetFlag": false,
            "types": [
              "string"
            ]
          }
        },
        "exampleSetFlag": false,
        "types": [
          "object"
        ]
      }
    },
    "extensions": {    }
  }
}

Result (it's UI hence the screenshot):

enter image description here

My Swagger dependencies are:

<!-- API Gateway -->
        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-starter-webflux-ui</artifactId>
            <version>2.0.4</version>
        </dependency>

Gateway: Java 17, Boot 3

<!-- microservice -->
        <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>

Microservice: Java 17, Boot 2

I obviously want it to group by controllers, as it usually does

Why is Swagger doing it?


Solution

  • Here's what I found

    Swagger UI groups endpoints by tags

    If no custom tags are added (with @Tag), Swagger will automatically create a tag for your controller class that matches that class's name (e.g. example-controller). Class-level tags (including default ones Swagger creates) will propagate to every endpoint declared within that controller class

    If a controller class or at least one of its methods has its own tag, Swagger will not create a default tag for that class. Even if you then manually remove your custom tag from an OpenApi object before serving it to Swagger UI, you still won't have that default tag. What that means is that those tag-less endpoints will be grouped into the "default" group