Search code examples
javaxmlspringrestjersey

Jersey client can't deserialize the XML output: message body reader was not found


I'm using the old com.sun.jersey.jersey-client@1.19.4 library to invoke a POST on a private third-party REST service, using the application/x-www-form-urlencoded content-type.

Everything seems to behave just as expected, however, the response isn't being automatically deserialized to my POJO. The exception is a ClientHandlerException, claiming there's no message body parser for that POJO.

The available default providers are:

com.sun.jersey.core.impl.provider.entity.FormProvider com.sun.jersey.core.impl.provider.entity.StringProvider com.sun.jersey.core.impl.provider.entity.ByteArrayProvider com.sun.jersey.core.impl.provider.entity.FileProvider com.sun.jersey.core.impl.provider.entity.InputStreamProvider com.sun.jersey.core.impl.provider.entity.DataSourceProvider com.sun.jersey.core.impl.provider.entity.XMLJAXBElementProvider$General com.sun.jersey.core.impl.provider.entity.ReaderProvider com.sun.jersey.core.impl.provider.entity.DocumentProvider com.sun.jersey.core.impl.provider.entity.SourceProvider$StreamSourceReader com.sun.jersey.core.impl.provider.entity.SourceProvider$SAXSourceReader com.sun.jersey.core.impl.provider.entity.SourceProvider$DOMSourceReader com.sun.jersey.json.impl.provider.entity.JSONJAXBElementProvider$General com.sun.jersey.json.impl.provider.entity.JSONArrayProvider$General com.sun.jersey.json.impl.provider.entity.JSONObjectProvider$General com.sun.jersey.core.impl.provider.entity.XMLRootElementProvider$General com.sun.jersey.core.impl.provider.entity.XMLListElementProvider$General com.sun.jersey.core.impl.provider.entity.XMLRootObjectProvider$General com.sun.jersey.core.impl.provider.entity.EntityHolderReader com.sun.jersey.json.impl.provider.entity.JSONRootElementProvider$General com.sun.jersey.json.impl.provider.entity.JSONListElementProvider$General com.sun.jersey.json.impl.provider.entity.JacksonProviderProxy

The expected third-party service XML output template:

<foobar>
    <foo>Foooooooo</foo>
    <bar>Barrrrrrr</bar>
</foobar>

The POJO:

@XmlRootElement(name = "foobar")
@XmlAccessorType(XmlAccessType.FIELD)
public class Foobar {

    @XmlElement(name = "foo")
    private String foo;

    @XmlElement(name = "bar")
    private String bar;

    public String getFoo() {
        return foo;
    }

    public void setFoo(String foo) {
        this.foo = foo;
    }

    public String getBar() {
        return bar;
    }

    public void setBar(String bar) {
        this.bar = bar;
    }

}

The request:

MultivaluedMap parameters = new MultivaluedMapImpl();

parameters.add(...);

Foobar response = client.resource(URL)
        .type(MediaType.APPLICATION_FORM_URLENCODED)
        .accept(MediaType.APPLICATION_XML)
        .post(Foobar.class, parameters);

The exception:

Exception in thread "main" com.sun.jersey.api.client.ClientHandlerException: A message body reader for Java class foo.bar.Foobar, and Java type class foo.bar.Foobar, and MIME media type text/html; charset=UTF-8 was not found at com.sun.jersey.api.client.ClientResponse.getEntity(ClientResponse.java:630) at com.sun.jersey.api.client.ClientResponse.getEntity(ClientResponse.java:586) at com.sun.jersey.api.client.WebResource.handle(WebResource.java:686) at com.sun.jersey.api.client.WebResource.access$200(WebResource.java:74) at com.sun.jersey.api.client.WebResource$Builder.post(WebResource.java:570) at foo.bar.Main.jerseySample(Main.java:103) at foo.bar.Main.main(Main.java:109)

Using the JAXB unmarshaller to deserialize the XML string works just fine, however Jersey can't do it on it's own (perhaps with the help of XMLJAXBElementProvider, XMLRootElementProvider, XMLRootObjectProvider?). What's going on here?


Solution

  • The cleanest way I found to solve this problem is to create a custom MessageBodyReader.

    public static class FoobarMessageBodyReader implements MessageBodyReader<Foobar> {
    
        private Unmarshaller unmarshaller;
    
        public RespostaIncluirMessageBodyReader() throws JAXBException {
            unmarshaller = JAXBContext.newInstance(Foobar.class).createUnmarshaller();
        }
    
        @Override
        public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
            return type.isAssignableFrom(Foobar.class);
        }
    
        @Override
        public Foobar readFrom(Class<Foobar> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, String> httpHeaders, InputStream entityStream) throws IOException, WebApplicationException {
            try {
                return (Foobar) unmarshaller.unmarshal(entityStream);
            }
            catch (JAXBException e) {
                throw new IOException("Could not unmarshal the XML output", e);
            }
        }
    
    }
    

    ...and add it to the client configuration, like this:

    ClientConfig clientConfig = new DefaultClientConfig();
    clientConfig.getClasses().add(FoobarMessageBodyReader.class);
    Client client = Client.create(clientConfig);
    

    You should now have your object deserialized successfully.