Repository navigation
Wrong response Content-Type in ExceptionHandler after adding dependency "spring-boot-starter-data-rest" #43143
Description
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
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-rest' // <-- a new dependecy has been added
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml'
}
Controller
@RestController
@RequestMapping("/api/users")
public class UserController {
@GetMapping("/test-user")
public ResponseEntity<User> getUser() {
User user = new User("test-user");
return ResponseEntity
.status(HttpStatus.OK)
.body(user);
}
@GetMapping("/test-user-error")
public User getUserException() {
throw new MyCustomException("my-custom-error");
}
}
Controller Advice
@RestControllerAdvice
public class AdviceController {
@ExceptionHandler
public ResponseEntity<ErrorsResponseDto> handleError(MyCustomException e) {
return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body(new ErrorsResponseDto(e.getMessage()));
}
}
Behaviour with spring-boot-starter-data-rest dependency
curl -v http://127.0.0.1:8080/api/users/test-user
* Trying 127.0.0.1:8080...
* Connected to 127.0.0.1 (127.0.0.1) port 8080
> GET /api/users/test-user HTTP/1.1
> Host: 127.0.0.1:8080
> User-Agent: curl/8.9.1
> Accept: */*
>
< HTTP/1.1 200
< Content-Type: application/json
< Transfer-Encoding: chunked
< Date: Wed, 13 Nov 2024 15:47:38 GMT
<
{"name":"test-user"}* Connection #0 to host 127.0.0.1 left intact
logs
2024-11-13T18:47:38.285+03:00 DEBUG 14812 --- [nio-8080-exec-3] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to com.example.demo.UserController#getUser()
2024-11-13T18:47:38.302+03:00 DEBUG 14812 --- [nio-8080-exec-3] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Using 'application/json', given [*/*] and supported [application/json, application/*+json, application/xml;charset=UTF-8, text/xml;charset=UTF-8, application/*+xml;charset=UTF-8]
2024-11-13T18:47:38.304+03:00 DEBUG 14812 --- [nio-8080-exec-3] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Writing [User[name=test-user]]
Response content-type is application/json. This is OK
curl -v http://127.0.0.1:8080/api/users/test-user-error
> curl -v http://127.0.0.1:8080/api/users/test-user-error
* Trying 127.0.0.1:8080...
* Connected to 127.0.0.1 (127.0.0.1) port 8080
> GET /api/users/test-user-error HTTP/1.1
> Host: 127.0.0.1:8080
> User-Agent: curl/8.9.1
> Accept: */*
>
* Request completely sent off
< HTTP/1.1 400
< Content-Type: application/xml;charset=UTF-8
< Transfer-Encoding: chunked
< Date: Wed, 13 Nov 2024 15:51:22 GMT
< Connection: close
<
<ErrorsResponseDto><error>my-custom-error</error></ErrorsResponseDto>* shutting down connection #0
logs
2024-11-13T18:51:22.447+03:00 DEBUG 14812 --- [nio-8080-exec-6] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to com.example.demo.UserController#getUserException()
2024-11-13T18:51:22.451+03:00 DEBUG 14812 --- [nio-8080-exec-6] .m.m.a.ExceptionHandlerExceptionResolver : Using @ExceptionHandler com.example.demo.AdviceController#handleError(MyCustomException)
2024-11-13T18:51:22.454+03:00 DEBUG 14812 --- [nio-8080-exec-6] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Using 'application/xml;charset=UTF-8', given [*/*] and supported [application/xml;charset=UTF-8, text/xml;charset=UTF-8, application/*+xml;charset=UTF-8, application/json, application/*+json]
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
* Trying 127.0.0.1:8080...
* Connected to 127.0.0.1 (127.0.0.1) port 8080
> GET /api/users/test-user-error HTTP/1.1
> Host: 127.0.0.1:8080
> User-Agent: curl/8.9.1
> Accept: */*
>
* Request completely sent off
< HTTP/1.1 400
< Content-Type: application/json
< Transfer-Encoding: chunked
< Date: Wed, 13 Nov 2024 15:56:57 GMT
< Connection: close
<
{"error":"my-custom-error"}* shutting down connection #0
logs
2024-11-13T18:56:57.825+03:00 DEBUG 2720 --- [nio-8080-exec-2] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to com.example.demo.UserController#getUserException()
2024-11-13T18:56:57.834+03:00 DEBUG 2720 --- [nio-8080-exec-2] .m.m.a.ExceptionHandlerExceptionResolver : Using @ExceptionHandler com.example.demo.AdviceController#handleError(MyCustomException)
2024-11-13T18:56:57.858+03:00 DEBUG 2720 --- [nio-8080-exec-2] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Using 'application/json', given [*/*] and supported [application/json, application/*+json, application/xml;charset=UTF-8, text/xml;charset=UTF-8, application/*+xml;charset=UTF-8]
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.
Activity
ivouchak-sc commentedon Nov 13, 2024
The sample application for quick reproducing:
demo.zip
nosan commentedon 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. (SeeRepositoryRestMvcConfiguration.extendHandlerExceptionResolvers(...)). ThisHandlerExceptionResolveris configuredwith default
HttpMessageConverterbeans andRepositoryRestMvcConfigurationdoes not re-order them as Spring Bootdoes in
org.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:
Honestly, this isn’t the ideal solution. Maybe someone has a better suggestion.
The other option is pretty straightforward:
nosan commentedon Nov 13, 2024
Maybe, Spring Boot could also re-order
HttpMessageConverterinSpringBootRepositoryRestConfigurerfor Spring Data REST.bclozel commentedon Nov 15, 2024
Thanks for the analysis @nosan, you're spot on.
I think this is an unfortunate combination of several valid opinions:
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.HttpMessageConverterfrom the application context, as ordered by Spring Framework and the application.We could consider several options here:
RepositoryRestConfigurerand reorder converters as you see fit. This is the code snippet shown by @nosan in a comment aboveWhile 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:
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!