Repository navigation
Wrong response Content-Type in ExceptionHandler after adding dependency "spring-boot-starter-data-rest" #43143
Description
Activity
The sample application for quick reproducing:
demo.zip- addedstatus: waiting-for-triageAn issue we've not yet triagedAn issue we've not yet triaged
on Nov 13, 2024 Thanks for the detailed description and sample, @ivouchak-sc
It does not work with
org.springframework.boot:spring-boot-starter-data-restbecauseRepositoryRestMvcConfigurationadds itsHandlerExceptionResolverto the beginning of the list. (See
RepositoryRestMvcConfiguration.extendHandlerExceptionResolvers(...)). ThisHandlerExceptionResolveris configured
with defaultHttpMessageConverterbeans andRepositoryRestMvcConfigurationdoes not re-order them as Spring Boot
does inorg.springframework.boot.autoconfigure.http.HttpMessageConverters.reorderXmlConvertersToEnd(...).Since this
HandlerExceptionResolverwas added to the beginning of the list and converters were not re-ordered you got a response in XML format.At the moment, to fix this issue, you can add the following bean:
@Bean RepositoryRestConfigurer reorderHttpMessageConvertersRepositoryRestConfigurer() { return new RepositoryRestConfigurer() { @Override public void configureExceptionHandlerExceptionResolver(ExceptionHandlerExceptionResolver exceptionResolver) { List<HttpMessageConverter<?>> messageConverters = exceptionResolver.getMessageConverters(); reorderXmlConvertersToEnd(messageConverters); } }; } private void reorderXmlConvertersToEnd(List<HttpMessageConverter<?>> converters) { List<HttpMessageConverter<?>> xml = new ArrayList<>(); for (Iterator<HttpMessageConverter<?>> iterator = converters.iterator(); iterator.hasNext(); ) { HttpMessageConverter<?> converter = iterator.next(); if ((converter instanceof AbstractXmlHttpMessageConverter) || (converter instanceof MappingJackson2XmlHttpMessageConverter)) { xml.add(converter); iterator.remove(); } } converters.addAll(xml); }
Honestly, this isn’t the ideal solution. Maybe someone has a better suggestion.
The other option is pretty straightforward:
@Bean RepositoryRestConfigurer reorderHttpMessageConvertersRepositoryRestConfigurer() { return new RepositoryRestConfigurer() { @Override public void configureExceptionHandlerExceptionResolver( ExceptionHandlerExceptionResolver exceptionResolver) { exceptionResolver.getMessageConverters().add(0, new MappingJackson2HttpMessageConverter()); } }; }
Maybe, Spring Boot could also re-order
HttpMessageConverterinSpringBootRepositoryRestConfigurerfor Spring Data REST.- addedfor: team-meetingAn issue we'd like to discuss as a team to make progressAn issue we'd like to discuss as a team to make progress
on Nov 14, 2024 - removedfor: team-meetingAn issue we'd like to discuss as a team to make progressAn issue we'd like to discuss as a team to make progress
on Nov 14, 2024 Thanks for the analysis @nosan, you're spot on.
I think this is an unfortunate combination of several valid opinions:
- Spring Boot reorder
HttpMessageConverterinstances to put XML ones last. Spring Boot more or less considers that XML is a bit out of fashion and unless asked explicitly, other converters should be used first. - Spring Data REST contributes an error handler with specific opinions (including some provided by users). It's ordering this handler first. It's also getting
HttpMessageConverterfrom the application context, as ordered by Spring Framework and the application.
We could consider several options here:
- provide your own
RepositoryRestConfigurerand reorder converters as you see fit. This is the code snippet shown by @nosan in a comment above - the shorter option somehow works in this case but adds a new JSON converter at the top of the list. This is not the converter that is auto-configured so it's unlikely to honor other auto-configurations and preferences in the app.
- Spring Boot reorders the converters for Spring Data REST. Unfortunately, that configuration is very much internal and unpacking this would be brittle. Also, other developers might be relying on this behavior for many years.
- Spring Framework changes the default order of converters, putting JSON first. I have created Revisit converters and codecs default setup in HTTP stacks spring-framework#33894 to consider this for Framework 7.0. A major generation is a good fit for such an change.
While I understand the lack of consistency here, the fact is that neither the HTTP client nor the application are expressing any hint for the content negotiation. In this case, I would argue that you can hardly expect any specific content-type in that situation.
Here, the easiest way out in the application would be to set a JSON media type in the
@ExceptionHandlerif you expect clients to always use JSON. Same goes for REST endpoints.I also think that setting a default content type (if the content negotiation comes up with nothing) like the following should work:
@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void configureContentNegotiation(ContentNegotiationConfigurer configurer) { configurer.defaultContentType(MediaType.APPLICATION_JSON); } }
Unfortunately, the
ExceptionHandlerExceptionResolverhere does not set theContentNegotiationManagerthat's available globally. Maybe doing so would solve this particular problem transparently. Could you maybe create an issue and discuss that point with the team?I'm closing this issue since the two most viables options should be explored in other projects.
Thanks!
- Spring Boot reorder
- addedstatus: invalidAn issue that we don't feel is validAn issue that we don't feel is validfor: external-projectFor an external project and not something we can fixFor an external project and not something we can fixand removedstatus: waiting-for-triageAn issue we've not yet triagedAn issue we've not yet triaged
on Nov 15, 2024
Spring Boot Version: 3.3.5.
Response content type changed from application/json to application/xml after adding new dependency org.springframework.boot:spring-boot-starter-data-rest.
build.gradle
Controller
Controller Advice
Behaviour with spring-boot-starter-data-rest dependency
curl -v http://127.0.0.1:8080/api/users/test-user
logs
Response content-type is application/json. This is OK
curl -v http://127.0.0.1:8080/api/users/test-user-error
logs
Response content-type is application/xml. This is WRONG. The content type should be application/json.
Behaviour without spring-boot-starter-data-rest dependency
Let try to remove dependency org.springframework.boot:spring-boot-starter-data-rest and repeat the last HTTP request again.
curl -v http://127.0.0.1:8080/api/users/test-user-error
logs
Response content-type is application/json. This is behaviour should be the same with added spring-boot-starter-data-rest dependency.
After logs analyzing the order of supported content types is differ.
The sample application for reproducing the issue is attached below.