Linked Questions

Popular Questions

Recently I found that if we add some parameters in the accept-header, it's hard for Spring to get correct response header due to it's isCompatibleWith() method in org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor


For example, if we have an endpoint like this:

@ApiResponse(code = ...)
@RequestMapping(value = {/Id}, method = RequestMethod.GET, produces = {
        MediaType.APPLICATION_JSON_VALUE + "; profile='http://profiles/v1'",
        MediaType.APPLICATION_JSON_VALUE + "; profile='http://profiles/v2'",
        MediaType.APPLICATION_JSON_VALUE + "; profile='http://profiles/v3'",})
@ResponseBody
public HttpEntity<? ..> getXXX(@PathVariable(value = "Id") String Id) throws Exception {
    // business logic

}    

Now, if in request-header I give:

  1. Accept = " " or No Accept header => it will return Content-Type as "application/json; profile='http://profiles/v1'" as expected.
  2. Accept = "application/json; profile='http://profiles/v3'" => it will return Content-Type as same because there is a match in the producibleMediaTypes.
  3. Accept = "application/json; profile='http://invalid/profiles/v1'" => there is no match in the producibleMediaTypes, as expected it should return a default Content-Type which is the first one in producibleMediaTypes. But now, it will return exactly the same value we provide in the Request-Header , for the above one, I will get "application/json; profile='http://invalid/profiles/v1'"

After debugging, I found that:

  1. For the first scenario, Spring will automatically set the Accept-Header as "*/*" before it goes into the writeWithMessageConverters(), which means after matching with producibleMediaTypes, it will pass the default Content-Type based on its comparison strategies.
  2. For the second scenario, since it has a perfect match in producibleMediaTypes, it will return the correct Content-Type.
  3. For the Error scenario, this is how Spring deal with @ResponseBody :
protected <T> void writeWithMessageConverters(T returnValue, MethodParameter returnType,
      ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
      throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

   Class<?> returnValueClass = getReturnValueType(returnValue, returnType);
   Type returnValueType = getGenericType(returnType);
   HttpServletRequest servletRequest = inputMessage.getServletRequest();
   List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(servletRequest);
   List<MediaType> producibleMediaTypes = getProducibleMediaTypes(servletRequest, returnValueClass, returnValueType);

   if (returnValue != null && producibleMediaTypes.isEmpty()) {
      throw new IllegalArgumentException("No converter found for return value of type: " + returnValueClass);
   }

   Set<MediaType> compatibleMediaTypes = new LinkedHashSet<MediaType>();
   for (MediaType requestedType : requestedMediaTypes) {
      for (MediaType producibleType : producibleMediaTypes) {
         if (requestedType.isCompatibleWith(producibleType)) {
            compatibleMediaTypes.add(getMostSpecificMediaType(requestedType, producibleType));
         }
      }
   }
   if (compatibleMediaTypes.isEmpty()) {
      if (returnValue != null) {
         throw new HttpMediaTypeNotAcceptableException(producibleMediaTypes);
      }
      return;
   }

requestedMediaTypes is what we passed in Request-Header, producibleMediaTypes is what we provided in @ReqeustMapping, what I understand is, after comparing, ***Spring* will choose more specific one as the selectedMediaType which is going to be returned as Response-Header. However, the invalid one was always selected as the more specific one.


Here is some discussion on Github:

Use parameters declared in consumes or produces condition to narrow the request mapping [SPR-17133]

Content negotiation ignores media type parameters [SPR-10903]

it seems they want to improve the compatible strategy or somehow allow the developers to implement their own parameter matching, instead of waiting for this, is there any other way to handle the invalid content-type parameter?

Like if is there any way to get the producibleMediaTypes before matching? If so, we can mark the invalid content-type as "*/*, then we can get the default one as expected. Or can we check the content-type again before we pass it to the Response-Header?

Have you ever met this situation before? Any advice or experience will help!

Related Questions