Prevent ResponseEntity being generated as OpenAPI model
When using Springfox you can annotate your endpoints to automatically generate OpenAPI docs for your clients.
This blogpost will show how you can prevent Springfox generating a model on an endpoint with ResponseEntity
as return type.
I’ll also cover how to prevent generating default responses.
An Example
Take an endpoint like below.
You want to return ResponseEntity
because you want control over the status and body which is returned within your endpoint code.
@PostMapping
@ApiOperation(value = "Create a Object")
@ApiResponses({
@ApiResponse(code = 202, message = "Object is accepted."),
@ApiResponse(code = 400, message = "Request is not valid", response = ErrorResponse.class)
})
public ResponseEntity createObject(
@RequestBody @Valid CreateObjectRequest request) {
try {
// handle object creation
return accepted().build();
} catch (Throwable throwable) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ErrorResponse.builder()
.errorCode("request.failed")
.message(exception.getMessage())
.build()
);
}
}
Click to see the Spingfox configuration used for this example
@Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.securitySchemes(List.of(apiKey))
.securityContexts(List.of(securityContext))
.apiInfo(metaData())
.host(swaggerHost)
.forCodeGeneration(true)
.select()
.apis(RequestHandlerSelectors.withClassAnnotation(RestController.class))
.build();
}
Now your generated OpenAPI doc contains responses with a $ref
to ResponseEntity
.
{
"responses": {
"202": {
"description": "Object is accepted.",
"schema": {
"originalRef": "ResponseEntity",
"$ref": "#/definitions/ResponseEntity"
}
}
}
}
Springfox will also generate default responses for 201
, 202
, 400
, 401
, 403
, 404
, which you may never need.
Click to see the generated definition for ResponseEntity
(it is quite long)
{
"definitions": {
"ResponseEntity": {
"type": "object",
"properties": {
"body": {
"type": "object"
},
"statusCode": {
"type": "string",
"enum": [
"100 CONTINUE",
- "101 SWITCHING_PROTOCOLS",
- "102 PROCESSING",
- "103 CHECKPOINT",
"200 OK",
"201 CREATED",
"202 ACCEPTED",
"203 NON_AUTHORITATIVE_INFORMATION",
- "204 NO_CONTENT",
- "205 RESET_CONTENT",
- "206 PARTIAL_CONTENT",
- "207 MULTI_STATUS",
- "208 ALREADY_REPORTED",
- "226 IM_USED",
- "300 MULTIPLE_CHOICES",
- "301 MOVED_PERMANENTLY",
- "302 FOUND",
- "302 MOVED_TEMPORARILY",
- "303 SEE_OTHER",
- "304 NOT_MODIFIED",
- "305 USE_PROXY",
- "307 TEMPORARY_REDIRECT",
- "308 PERMANENT_REDIRECT",
"400 BAD_REQUEST",
"401 UNAUTHORIZED",
"402 PAYMENT_REQUIRED",
"403 FORBIDDEN",
"404 NOT_FOUND",
"405 METHOD_NOT_ALLOWED",
- "406 NOT_ACCEPTABLE",
- "407 PROXY_AUTHENTICATION_REQUIRED",
- "408 REQUEST_TIMEOUT",
- "409 CONFLICT",
- "410 GONE",
- "411 LENGTH_REQUIRED",
- "412 PRECONDITION_FAILED",
- "413 PAYLOAD_TOO_LARGE",
- "413 REQUEST_ENTITY_TOO_LARGE",
- "414 URI_TOO_LONG",
- "414 REQUEST_URI_TOO_LONG",
- "415 UNSUPPORTED_MEDIA_TYPE",
- "416 REQUESTED_RANGE_NOT_SATISFIABLE",
- "417 EXPECTATION_FAILED",
- "418 I_AM_A_TEAPOT",
- "419 INSUFFICIENT_SPACE_ON_RESOURCE",
- "420 METHOD_FAILURE",
- "421 DESTINATION_LOCKED",
- "422 UNPROCESSABLE_ENTITY",
- "423 LOCKED",
- "424 FAILED_DEPENDENCY",
- "426 UPGRADE_REQUIRED",
- "428 PRECONDITION_REQUIRED",
- "429 TOO_MANY_REQUESTS",
- "431 REQUEST_HEADER_FIELDS_TOO_LARGE",
- "451 UNAVAILABLE_FOR_LEGAL_REASONS",
- "500 INTERNAL_SERVER_ERROR",
- "501 NOT_IMPLEMENTED",
- "502 BAD_GATEWAY",
- "503 SERVICE_UNAVAILABLE",
- "504 GATEWAY_TIMEOUT",
- "505 HTTP_VERSION_NOT_SUPPORTED",
- "506 VARIANT_ALSO_NEGOTIATES",
- "507 INSUFFICIENT_STORAGE",
- "508 LOOP_DETECTED",
- "509 BANDWIDTH_LIMIT_EXCEEDED",
- "510 NOT_EXTENDED",
- "511 NETWORK_AUTHENTICATION_REQUIRED"
]
},
"statusCodeValue": {
"type": "integer",
"format": "int32"
}
},
"title": "ResponseEntity"
}
}
}
The solution
In the example above you’ve seen ResponseEntity is being generated as OpenAPI model, while you only wanted to return a 200
without body.
Springfox has a configuration solution for this: directModelSubstitute(ResponseEntity.class, Void.class)
.
We’ve also seen Springfox generates default responses, which you may never need.
This can be resolved with the configuration of useDefaultResponseMessages(false)
.
Click to see the updated configuration
return new Docket(DocumentationType.SWAGGER_2)
.securitySchemes(List.of(apiKey))
.securityContexts(List.of(securityContext))
.apiInfo(metaData())
.host(swaggerHost)
.forCodeGeneration(true)
.useDefaultResponseMessages(false) // do not use default response messages
.directModelSubstitute(ResponseEntity.class, Void.class) // substitute ResponseEntity as Void
.select()
.apis(RequestHandlerSelectors.withClassAnnotation(RestController.class))
.build();
Now the Responses generates are:
{
"200": {
"description": "OK"
},
"202": {
"description": "Object is accepted."
},
"400": {
"description": "Request is not valid",
"schema": {
"originalRef": "ErrorResponse",
"$ref": "#/definitions/ErrorResponse"
}
}
}
As you can see, it has generated a 200
response for you, but in the @ApiResponses
you’ve only defined 202
and 400
.
This not as expected, but can be resolved by adding a @ResponseStatus(ACCEPTED)
to your endpoint and the 200
response will be omitted.
With all the configuration done your updated endpoint will look like this
@ResponseStatus(HttpStatus.ACCEPTED)
@PostMapping
@ApiOperation(value = "Create a Object")
@ApiResponses({
@ApiResponse(code = 202, message = "Object is accepted."),
@ApiResponse(code = 400, message = "Request is not valid", response = ErrorResponse.class)
})
public ResponseEntity createObject(
@RequestBody @Valid CreateObjectRequest request) {
try {
// handle object creation
return accepted().build();
} catch (Throwable throwable) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ErrorResponse.builder()
.errorCode("request.failed")
.message(exception.getMessage())
.build()
);
}
}
and the generate OpenAPI responses like this
{
"202": {
"description": "Object is accepted."
},
"400": {
"description": "Request is not valid",
"schema": {
"originalRef": "ErrorResponse",
"$ref": "#/definitions/ErrorResponse"
}
}
}
Written using Springfox 2.9.x and SpringBoot 2.1.x