Suppose I have a controller that serves GET
request and returns bean to be serialized to JSON and also provides an exception handler for IllegalArgumentEx
There are several aspects relating to the problem:
StringHttpMessageConverter
adds catch-all mime type */*
to the list of supported media types, while MappingJackson2HttpMessageConverter
is bound to application/json
only.@RequestMapping
is providing produces = ...
, this value is stored in HttpServletRequest
(see RequestMappingInfoHandlerMapping.handleMatch()
) and when the error handler is called, this mime type is automatically inherited and used.The solution in simple case would be to put StringHttpMessageConverter
first in the list:
and also remove produces
from @RequestMapping
annotation:
@RequestMapping(value = "/meta/{itemId}", method = RequestMethod.GET)
@ResponseBody
public MetaInformation getMetaInformation(@PathVariable int itemId) {
return myService.getMetaInformation(itemId);
}
Now:
StringHttpMessageConverter
will discard all types, which only MappingJackson2HttpMessageConverter
can handle (MetaInformation
, java.util.Collection
, etc) allowing them to be passed further.StringHttpMessageConverter
will take the precedence.So far so good, but unfortunately things get more complicated with ObjectToStringHttpMessageConverter. For handler return type java.util.Collection
this message convertor will report that it can convert this type to java.lang.String
. The limitation comes from the fact that collection element types are erased and AbstractHttpMessageConverter.canWrite(Class> clazz, MediaType mediaType)
method gets java.util.Collection>
class for check, however when it comes to conversion step ObjectToStringHttpMessageConverter
fails. To solve the problem we keep produces
for @RequestMapping
annotation where JSON convertor should be used, but to force correct content type for exception handler, we will erase HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE
attribute from HttpServletRequest
:
@ExceptionHandler(IllegalArgumentException.class)
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
@ResponseBody
public String handleIllegalArgumentException(HttpServletRequest request, IllegalArgumentException ex) {
request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
return ExceptionUtils.getStackTrace(ex);
}
@RequestMapping(value = "/meta", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public Collection getMetaInformations() {
return myService.getMetaInformations();
}
Context stays the same as it was originally:
Now scenarios (1,2,3,4) are handled correctly because of content-type negotiation, and scenarios (5,6) are processed in exception handler.
Alternatively one can replace collection return type with arrays, then solution #1 is applicable again:
@RequestMapping(value = "/meta", method = RequestMethod.GET)
@ResponseBody
public MetaInformation[] getMetaInformations() {
return myService.getMetaInformations().toArray();
}
For discussion:
I think that AbstractMessageConverterMethodProcessor.writeWithMessageConverters()
should not inherit class from value, but rather from method signature:
Type returnValueType = returnType.getGenericParameterType();
and HttpMessageConverter.canWrite(Class> clazz, MediaType mediaType)
should be changed to:
canWrite(Type returnType, MediaType mediaType)
or (in case it is too limiting potential class-based convertors) to
canWrite(Class> valueClazz, Type returnType, MediaType mediaType)
Then parametrized types could be handled correctly and solution #1 would be applicable again.