When custom HttpMessageConverter is used malformed api-docs endpoint produces malformed JSON
Steps to reproduce the behavior:
org.springdoc.webmvc.api.OpenApiResource#openapiJson. The original response is wrapped with a leading and trailing double quote and all the double internal double quotes are escaped:"{\"openapi\":\"3.0.1\",\"info\":{\"title\":\"OpenAPI definition\",\"version\":\"v0\"},\"servers\":[{\"url\":\"http://localhost:8080\",\"description\":\"Generated server url\"}],\"paths\":{\"/get\":{\"get\":{\"tags\":[\"controller\"],\"operationId\":\"getSomeMap\",\"responses\":{\"200\":{\"description\":\"default response\",\"content\":{\"*/*\":{\"schema\":{\"$ref\":\"#/components/schemas/ImmutableMultimapStringString\"}}}}}}}},\"components\":{\"schemas\":{\"ImmutableMultimapStringString\":{\"type\":\"object\",\"properties\":{\"empty\":{\"type\":\"boolean\"}}}}}}"Expected behavior
org.springdoc.webmvc.api.OpenApiResource#openapiJson should not do the conversion from OpenAPI to String. Just return OpenAPI or ResponseEntity<OpenAPI> and leave the serialization for the registered HttpMessageConverter.Additional context
An ugly fix for this problem is https://github.com/Geneea/springdoc-test/blob/master/src/main/java/com/geneea/springdoc/FixApiDocsWithFilter.java
registering a Filter what corrects the malformed response.
Same problem is described in https://stackoverflow.com/questions/59393191/endpoint-api-docs-doesnt-work-with-custom-gsonhttpmessageconverter
@peter-szepe-geneea,
Only one @Configuration class may have the @EnableWebMvc annotation to import the Spring Web MVC configuration.
If you can remove @EnableWebMvc from your sample, it will work directly.
If you can not, because you changing the default list of Http Converters with our own, you should override OpenApiResource as follow:
@Component
public class OpenApiResource extends org.springdoc.webmvc.api.OpenApiResource {
public OpenApiResource(OpenAPIBuilder openAPIBuilder, AbstractRequestBuilder requestBuilder, GenericResponseBuilder responseBuilder, OperationBuilder operationParser, RequestMappingInfoHandlerMapping requestMappingHandlerMapping, Optional<ActuatorProvider> servletContextProvider, Optional<List<OpenApiCustomiser>> openApiCustomisers, SpringDocConfigProperties springDocConfigProperties, Optional<SecurityOAuth2Provider> springSecurityOAuth2Provider) {
super(openAPIBuilder, requestBuilder, responseBuilder, operationParser, requestMappingHandlerMapping, servletContextProvider, openApiCustomisers, springDocConfigProperties, springSecurityOAuth2Provider);
}
@Operation(hidden = true)
@GetMapping(value = API_DOCS_URL, produces = MediaType.TEXT_PLAIN_VALUE)
public String openapiJson(HttpServletRequest request, @Value(API_DOCS_URL) String apiDocsUrl)
throws JsonProcessingException {
calculateServerUrl(request, apiDocsUrl);
OpenAPI openAPI = this.getOpenApi();
return Json.mapper().writeValueAsString(openAPI);
}
private void calculateServerUrl(HttpServletRequest request, String apiDocsUrl) {
String requestUrl = decode(request.getRequestURL().toString());
String calculatedUrl = requestUrl.substring(0, requestUrl.length() - apiDocsUrl.length());
openAPIBuilder.setServerBaseUrl(calculatedUrl);
}
}
OpenApiResource, will return TEXT_PLAIN_VALUE starting from the next release v1.3.5, so you won't need to override it for this use case.
@bnasslahsen The fix provided in https://github.com/springdoc/springdoc-openapi/commit/ee083330fecc03b176e09d063b5f0c66d756b621 is breaking all our automated tests for Swagger endpoints in all services. Is it possible to leave content-type to be json as it was previously, but instead of returning raw string-- return some dto, so that spring does all json conversion?
Hi @Aloren,
Changing the return type to DTO, is not easy. It require a custom serializer, which is already provided by swagger-core.
Also if your tests are based on the payload, Changing to DTO may change the result if we don't use the same serializer.
Can't you just adapt your failing tests by adapring the content-type ?
We can of course add additional workarounds for our tests, but the main issue is that such response format violates HTTP specs. With such change content negotiation is completely broken and whoever knows what functionality it might break in the future. This is a type of fix that gives short-term benefits, but may lead to interesting new issues :)
I would say that the impact should be limited because the return type was already of type String.
But i agree its better to return it as object.
Hopefully, If i have some time, i will have a look at it.
@Aloren,
This change is reverted back, as it seems more going to cause problems than resolving them.
@peter-szepe-geneea,
You can override the OpenApiResource as proposed earlier. This seems the less harmful solution
its now available on v1.3.7.
@bnasslahsen I'm using 1.3.9 but I have still the same problem???
openapi_version=1.3.9+ gson_version=2.8.6
implementation("org.springdoc:springdoc-openapi-webmvc-ui:${openapi_version}")
implementation("org.springdoc:springdoc-openapi-webmvc-core:${openapi_version}")
implementation("org.springdoc:springdoc-openapi-security:${openapi_version}")
implementation("org.springdoc:springdoc-openapi-kotlin:${openapi_version}")
implementation("com.google.code.gson:gson:$gson_version")
With SpringFox I can register an Adapter: https://github.com/springfox/springfox/issues/2758...
@MikeMitterer,
springdoc-openapi is built on top of swagger-core, which is based on jackson;
There is already existing request to add support for GSON, but not sure it will be added in short term:
As described before, you can override the OpenApiResource as follow:
@Component
public class OpenApiResource extends org.springdoc.webmvc.api.OpenApiResource {
public OpenApiResource(OpenAPIBuilder openAPIBuilder, AbstractRequestBuilder requestBuilder, GenericResponseBuilder responseBuilder, OperationBuilder operationParser, RequestMappingInfoHandlerMapping requestMappingHandlerMapping, Optional<ActuatorProvider> servletContextProvider, Optional<List<OperationCustomizer>> operationCustomizers, Optional<List<OpenApiCustomiser>> openApiCustomisers, SpringDocConfigProperties springDocConfigProperties, Optional<SecurityOAuth2Provider> springSecurityOAuth2Provider, Optional<RouterFunctionProvider> routerFunctionProvider) {
super(openAPIBuilder, requestBuilder, responseBuilder, operationParser, requestMappingHandlerMapping, servletContextProvider, operationCustomizers, openApiCustomisers, springDocConfigProperties, springSecurityOAuth2Provider, routerFunctionProvider);
}
@Operation(hidden = true)
@GetMapping(value = Constants.API_DOCS_URL, produces = MediaType.TEXT_PLAIN_VALUE)
public String openapiJson(HttpServletRequest request, @Value(Constants.API_DOCS_URL) String apiDocsUrl)
throws JsonProcessingException {
calculateServerUrl(request, apiDocsUrl);
OpenAPI openAPI = this.getOpenApi();
return Json.mapper().writeValueAsString(openAPI);
}
}
Hello, I have incorporated into my project version 1.3.9 OpenAPI to migrate from SpringFox and I have suffered the same problem that is discussed in this entry
implementation "org.springdoc:springdoc-openapi-ui:1.3.9"
We are removing the Jackson AutoConfiguration class from startup and manually configuring the HttpMessageConverter we need
@SpringBootApplication(exclude = {JacksonAutoConfiguration.class})
@Profile("dev")
public class AgrocietyDevelopmentApplication {
public static void main(final String[] args) {
SpringApplication.run(AgrocietyDevelopmentApplication.class, args);
}
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new JsonMergePatchHttpMessageConverter());
converters.add(new JsonPatchHttpMessageConverter());
converters.add(new MappingJackson2HttpMessageConverter(objectMapper));
converters.add(new ByteArrayHttpMessageConverter());
}
Should I inject the Jackson ObjectMapper into a class that inherits from 'OpenApiResource' and do the JSON conversion in the openApi method ? Or how should I deal with this problem?
Thank you very much for your attention, regards
@sergio11,
If you are using jackson, there is no need by default to override OpenApiResource, unless your are adding a custom HttpMessageConverter, which changes the default jackson behaviour.
You can find attached a sample code using: @SpringBootApplication(exclude = {JacksonAutoConfiguration.class})
If you are still having issues. Please make sure you provide a minimal reproducible example as the one i have attached.
@bnasslahsen
Thank you very much for the response and the example provided. I have reduced the project to the minimum to show you the problem, I would ask you please if you can verify what is wrong in this example project.
I have left a simple controller and the WebConfig class with the ObjectMapper that we use the project.

Thank you very much for your attention, regards
@sergio11,
You need to add StringHttpMessageConverter to your method configureMessageConverters,
(StringHttpMessageConverter has to be the first one):
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new StringHttpMessageConverter());
converters.add(new MappingJackson2HttpMessageConverter(provideObjectMapper()));
converters.add(new ByteArrayHttpMessageConverter());
}
You can find attached, the working example from the source that you have provided.
Most helpful comment
@sergio11,
You need to add StringHttpMessageConverter to your method configureMessageConverters,
(StringHttpMessageConverter has to be the first one):
You can find attached, the working example from the source that you have provided.
spring_open_api_test-corrected.zip