Showing posts with label swagger. Show all posts
Showing posts with label swagger. Show all posts

Monday, April 7, 2025

Spring Boot 3 serverside forwarding

Introduction

Sometimes you want to forward to another URL on the serverside in Spring Boot 3. 


The example in this blogpost is for forwarding to the Swagger JSON open-api-specification OAS /api-docs resource listing. You might need this when you want to protect that URL via Spring authentication, so it has to first come in to a controller, and only then forward it.

Solution

Below a new controller ApiController is created, with as endpoint GET /open-api. That has bearer authentication on it. And redirects to /apidocs/api-definition. For details on what api-definitions i.e grouped Open API definitions are, see here.

ModelAndView

Using a ModelAndView from Spring MVC:

  @RestController
  @SecurityRequirement(name = "bearerAuth")
  public class ApiController() {
    @Operation(
      summary = "Gets the OpenApi specification of the API",
    )
    @GetMapping("/open-api", produces = ["application/json"])
    @SecurityRequirement(name = AUTHORIZATION)
    fun getOpenApiSpecification(): ModelAndView {
      return ModelAndView("forward:/api-docs/api-definition")
    }

To have this show up in Swagger, also set in application.yml:

  springdoc:
    model-and-view-allowed: true

Servlet context

Using the servlet context, so you don't need a ModelAndView:

  @GetMapping("/open-api", produces = ["application/json"])
  @ResponseStatus(HttpStatus.OK)
  @SecurityRequirement(name = AUTHORIZATION)
  fun getOpenApiSpecification(request: HttpServletRequest, response: HttpServletResponse) {
    request.session.servletContext.getRequestDispatcher("/api-docs/api-definition").forward(request, response)
  }

This dependency is then also needed: 
  <dependency>
    <groupId>jakarta.servlet</groupId>
    <artifactId>jakarta.servlet-api</artifactId>
    <version>6.1.0</version>
  </dependency










Wednesday, September 27, 2023

SpringDoc OpenAPI Swagger generated Swagger API shows incorrect class with same name

Introduction

When you have multiple classes with the same name in your classpath, SpringDoc with Swagger API annotations potentially picks the wrong class with the same name when generating the Swagger UI documentation.


Suppose you have these classes:

  • org.example.BookDto
  • org.example.domain.BookDto
     

And you specified your endpoint like this, where you want to have it use org.example.BookDto:

  @Operation(summary = "Get a list of books for a given shop")
  @ApiResponses(
    value = [
      ApiResponse(
        responseCode = "200",
        description = "A list of books",
        content = [Content(mediaType = "application/json",
                    array = ArraySchema(schema = Schema(implementation = BookDto::class)))]
      )
    ]
  )
  @GetMapping("/books/{shopId}")
  fun getBooksByShopId(
    @Parameter(description = "Shop to search for")
    @PathVariable shopId: Long
  ): List<BookDto> {
    return bookService.getBooksByShopId(shopId)
      .map { BooksMapper.mapDto(it) }
  }

Then whatever it finds first on the classpath will be visible in https://localhost:8080/swagger-ui.html. Not necessarily the class you meant, it might pick org.example.domain.BookDto.  

Setup:

  • Spring Boot 3
  • Kotlin 1.8
  • Springdoc OpenAPI 2.2.0
     

Solution

Several solutions exist:

Solution 1

Specify in your application.yml:

springdoc:
 use-fqn: true

 

Disadvantage: the Swagger documentation in the swagger-ui.html endpoint has then the fully specified package classpath + classname in it. Looks ugly. 

Solution 2

Setting it in the @Bean configuration:

import io.swagger.v3.core.jackson.TypeNameResolver
  @Bean
  fun openAPI(): OpenAPI? {

    TypeNameResolver.std.setUseFqn(true)
    return OpenAPI()
      .addServersItem(Server().url("/"))
      .info(
        Info().title("Books Microservice")
          .description("The Books Microservice")
          .version("v1")
      )
      .externalDocs(
        ExternalDocumentation()
          .description("Books Microservice documentation")
          .url("https://github.com/myproject/README.md")
      )
  }

Disadvantage: also in this solution the Swagger documentation in the swagger-ui.html endpoint has then the fully specified package classpath + classname in it. Looks ugly.

Solution 3

You can create your own ModelConverters, but that is much more work. Examples here:  https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Extensions#extending-core-resolver and https://groups.google.com/g/swagger-swaggersocket/c/kKM546QXGY0

Solution 4

Make sure for each endpoint you specify the response class with full class package path:

@Operation(summary = "Get a list of books for a given shop")
  @ApiResponses(
    value = [
      ApiResponse(
        responseCode = "200",
        description = "A list of books",
        content = [Content(mediaType = "application/json",
                    array = ArraySchema(schema = Schema(implementation =
org.example.BookDto::class)))]
      )
    ]
  )
  @GetMapping("/books/{shopId}")
  fun getBooksByShopId(
    @Parameter(description = "Shop to search for")
    @PathVariable shopId: Long
  ): List<BookDto> {
    return bookService.getBooksByShopId(shopId)
      .map { BooksMapper.mapDto(it) }
  }

 See the bold Schema implementation value for what changed.