HttpMediaTypeNotSupportedException - Or is it?

22 Dec 2011

Media type not supported?

This post is more of a reminder for future me… because this is one of these errors that doesn't make much sense until you figure it out. After a relatively big refactor, one of our RESTful controllers started failing with the following exception:

org.springframework.web.HttpMediaTypeNotSupportedException:
Content type 'application/json; charset=UTF-8' not supported

Why would this particular controller not support application/json anymore? Of course it turned out to be a red herring. Somewhere deep in Spring's code, the following exception was being thrown… and caught straight away:

org.codehaus.jackson.map.JsonMappingException:
Conflicting setter definitions for property "suburb":
au.com.myproject.Client#setSuburb(1 params) vs au.com.myproject.Client#setSuburb(1 params)

Behind the scenes

So what happened? When you try to call a controller, Spring will look at the content-type specified in the request header. It will then iterate over all possible deserialisers to see who can convert the payload into the appropriate Java object. Where we're not lucky is that Jackson says yes I should be able to handle application/json, but if it realises deserialisation is in fact not possible, it just bails - with no apparent logging.

try {
    deser = _createAndCacheValueDeserializer(config, type, null, null);
} catch (Exception e) {
    return false;
}

Spring then assumes no one could handle the given content-type, hence the HttpMediaTypeNotSupportedException.

Future-proofing with tests

To make sure this doesn't happen again, here's 10 lines of code that will hopefully prove useful in the future!

public class JsonDeserialisationTest
{

    MappingJacksonHttpMessageConverter converter = new MappingJacksonHttpMessageConverter();

    @Test
    public void allClassesUsedByOurControllersShouldBeDeserialisableByJackson() throws Exception
    {
        assertCanBeMapped(Client.class);
        assertCanBeMapped(Project.class);
        // ...
    }

    private void assertCanBeMapped(Class<?> classToTest)
    {
        String message =
            format("%s is not deserialisable, check the swallowed exception in StdDeserializerProvider.hasValueDeserializerFor",
            classToTest.getSimpleName());
        assertThat(message, converter.canRead(classToTest, MediaType.APPLICATION_JSON), is(true));
    }

}

With a shorter feedback cycle and better coverage of our integration layer, it's time to enjoy the holidays Smiley Happy holidays everyone!

Comments