Search code examples
javaspringdoc-openapi-ui

What should I use in place of @ApiOperation(response) and @ApiResponse(response) when migrating from Springfox to Springdoc?


I am currently migrating from springfox to springdoc-openapi-ui and have run into some issues that the migration documentation does not touch on.

I am getting the error Cannot resolve method 'response' at response = Example.class and response = ErrorResponse.class below.

I've tried using responses instead but had no luck.

With springfox, my code looked like this:

@ApiOperation(
        value = "sample summary",
        response = Example.class,
        notes = "sample description")
@ApiResponses(value = {
        @ApiResponse(code = 200, message = "Successful"),
        @ApiResponse(code = 400, message = "Bad Request", response = ErrorResponse.class),
        @ApiResponse(code = 401, message = "Not Authorized", response = ErrorResponse.class),
        @ApiResponse(code = 403, message = "Forbidden", response = ErrorResponse.class),
        @ApiResponse(code = 404, message = "Not Found", response = ErrorResponse.class)})
@PostMapping(value = {"/sampleEndpoint"}, produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)

after migrating to springdoc, here's where it stands:

@Operation(
        summary = "sample summary",
        response = Example.class,
        description = "sample description")
@ApiResponses(value = {
        @ApiResponse(responseCode = "200", description = "Successful"),
        @ApiResponse(responseCode = "400", description = "Bad Request", response = ErrorResponse.class),
        @ApiResponse(responseCode = "401", description = "Not Authorized", response = ErrorResponse.class),
        @ApiResponse(responseCode = "403", description = "Forbidden", response = ErrorResponse.class),
        @ApiResponse(responseCode = "404", description = "Not Found", response = ErrorResponse.class)})
@PostMapping(value = {"/sampleEndpoint"}, produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)

Solution

  • You can use

    content = @Content(mediaType =...,schema = @Schema(implementation=...))
    

    You didn't show the rest of your code, but I'm assuming you're returning your ErrorResponse class as json in the 4xx responses?

    If so, I think the spring-doc definition would look something like this:

    @ApiResponse(responseCode = "400", description = "Bad Request", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = ErrorResponse.class)))
    

    I couldn't find a direct example of this on the base spring-doc page, but how to hide a schema has something similar: https://springdoc.org/#how-can-i-hide-schema-of-the-the-response

    Edit for your comments:

    Since you specified the produces type in the PostMapping spring-doc will infer this for all responses by default, so there's no need to specify that in the @Content annotation.

    It's not entirely clear to me what your method signature looks like:

    1. Are you returning ResponseEntity<String> and mapping the json in the error response yourself?
    2. Or are you returning ResponseEntity<ErrorResponse> and then returning an empty body on 200?

    spring-doc will automatically attempt to infer the response schema based on the return type, so if you're doing the second one, you won't need to specify the @Content type at all (except on the 200 response where you're returning an empty body, where you'll want to specify just by using an empty @Content annotation to omit the schema).

    Example of #1

    If you'd like the schema (with examples) to show up in your spring-doc interface you're going to want to include the schema = @Schema(implementation = ...) portion.

    Here's a further example of what I think you might be trying to achieve:

        @Operation(summary = "sample summary",
                description = "sample description")
        @ApiResponses(value = {
                @ApiResponse(responseCode = "200", description = "Successful", content = @Content),
                @ApiResponse(responseCode = "400", description = "Bad Request", content =
                @Content(schema = @Schema(implementation = ErrorResponse.class))),
                @ApiResponse(responseCode = "401", description = "Not Authorized", content =
                @Content(schema = @Schema(implementation = ErrorResponse.class))),
                @ApiResponse(responseCode = "403", description = "Forbidden", content =
                @Content(schema = @Schema(implementation = ErrorResponse.class))),
                @ApiResponse(responseCode = "404", description = "Not Found", content =
                @Content(schema = @Schema(implementation = ErrorResponse.class)))
        })
        @PostMapping(value = {"/sampleEndpoint"}, produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
        public ResponseEntity<String> postSampleEndpoint() {
            // just a fake response here
            ErrorResponse exampleErrorResponse = new ErrorResponse("fakeErrorDescription", "fakeSuggestion");
            ObjectMapper mapper = new ObjectMapper();
            String jsonErrorResult;
            try {
                jsonErrorResult = mapper.writeValueAsString(exampleErrorResponse);
            } catch (JsonProcessingException e) {
                // fill the json error manually with something else if mapping fails
                throw new RuntimeException(e);
            }
            return new ResponseEntity<>(jsonErrorResult, HttpStatus.BAD_REQUEST);
        }
    

    Example of #2

        @Operation(summary = "sample summary",
                description = "sample description")
        @ApiResponses(value = {
                @ApiResponse(responseCode = "200", description = "Successful", content = @Content),
                @ApiResponse(responseCode = "400", description = "Bad Request"),
                @ApiResponse(responseCode = "401", description = "Not Authorized"),
                @ApiResponse(responseCode = "403", description = "Forbidden"),
                @ApiResponse(responseCode = "404", description = "Not Found")
        })
        @PostMapping(value = {"/sampleEndpoint"}, produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
        public ResponseEntity<ErrorResponse> postSampleEndpoint() {
            // just a fake response here
            ErrorResponse exampleErrorResponse = new ErrorResponse("fakeErrorDescription", "fakeSuggestion");
            return new ResponseEntity<>(exampleErrorResponse, HttpStatus.BAD_REQUEST);
        }
    

    Then in the ErrorResponse model class you can use the @Schema annotation to provide examples:

    ErrorResponse.java:

    public class ErrorResponse {
        @JsonProperty("error_description")
        @Schema(example = "Example Error Description")
        private String errorDescription;
    
        @Schema(example = "Example Suggestion")
        private String suggestion;
    
        public ErrorResponse(String errorDescription, String suggestion) {
            this.errorDescription = errorDescription;
            this.suggestion = suggestion;
        }
    
        public String getErrorDescription() {
            return errorDescription;
        }
    
        public void setErrorDescription(String errorDescription) {
            this.errorDescription = errorDescription;
        }
    
        public String getSuggestion() {
            return suggestion;
        }
    
        public void setSuggestion(String suggestion) {
            this.suggestion = suggestion;
        }
    }
    

    Which generates a swagger doc UI like this:

    Spring_Doc_Swagger_Example