Retrofit: Can't put JAXB to work

Created on 23 Jan 2019  路  8Comments  路  Source: square/retrofit

What kind of issue is this?

  • [X] Bug report / Question.

I can't get the JaxbConverter to work. I can't tell if I'm missing somethin or if it's broken.
Java 8

build.gradle

implementation 'com.squareup.retrofit2:retrofit:2.5.0'
implementation 'com.squareup.retrofit2:converter-jaxb:2.5.0'

somewhere in the code

Retrofit retrofit = new Retrofit.Builder()
            .baseUrl("https://www.google.com/alerts/feeds/")
            .addConverterFactory(JaxbConverterFactory.create())
            .build();
service = retrofit.create(GoogleAlertService.class);

...

Response<GoogleAlertRssFeed> response = request.execute();
...
LOG.info(response.body().toString());

GoogleAlertRssFeed

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name = "feed", namespace = "http://www.w3.org/2005/Atom")
public class GoogleAlertRssFeed {

    @XmlElement(required = true)
    public String id;

    public GoogleAlertRssFeed() {
    }

    ...

    @Override
    public String toString() {
        return "GoogleAlertRssFeed{" +
                "id='" + id + '\'' +
            '}';
    }
}

And the XML looks like:

<?xml version="1.0" encoding="utf-8"?> <feed xmlns="http://www.w3.org/2005/Atom" xmlns:idx="urn:atom-extension:indexing"> <id>tag:google.com,2005:reader/user/xyz/state/com.google/alerts/xyz</id> <title>...

But despite the correct raw response the id is always null.

I tried anything I could find on the net.

  • Tried @XmlAccessorType(XmlAccessType.FIELD) on the class.
  • Tried with / without namespace.
  • Tried private field + getter & setter, with/ without @XmlAccessorType(XmlAccessType.FIELD).
  • Tried with / without @XmlElement(name = "id").
  • Tried adding a constructor that accepts the id.
  • Followed the test in the repo.

What am I doing wrong?

Resources:

Needs Info

Most helpful comment

After removing xmlns="http://www.w3.org/2005/Atom" xmlns:idx="urn:atom-extension:indexing" from the feed tag it works.
Seems to be a namespace issue.

All 8 comments

Try editing JaxbConverterFactoryTest.java, setting it's model type to your model type and it's XML to your XML? If that fails set some breakpoints?

Replaced the test's XML with

static final String SAMPLE_CONTACT_XML = "<?xml version=\"1.0\" encoding=\"utf-8\"?> <feed xmlns=\"http://www.w3.org/2005/Atom\" xmlns:idx=\"urn:atom-extension:indexing\"> <id>tag:google.com,2005:reader/user/abc/state/com.google/alerts/abc</id> </feed> ";
static final Feed SAMPLE_CONTACT = new Feed("tag:google.com,2005:reader/user/abc/state/com.google/alerts/abc");

Replace Contact with Feed:

package retrofit2.converter.jaxb;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name = "feed")
final class Feed {
  @XmlElement(required = true)
  public final String id;

  @SuppressWarnings("unused") // Used by JAXB.
  private Feed() {
    this("");
  }

  public Feed(String id) {
    this.id = id;
  }

  @Override public boolean equals(Object o) {
    return o instanceof Feed
        && ((Feed) o).id.equals(id);
  }

  @Override public int hashCode() {
    return Arrays.asList(id).hashCode();
  }
}

Changed occurrences of Contact with Feed, name with id and Jenny with tag:google.com,2005:reader/user/abc/state/com.google/alerts/abc in JaxbConverterFactoryTest.java.

Results:

Test set: retrofit2.converter.jaxb.JaxbConverterFactoryTest
-------------------------------------------------------------------------------
Tests run: 6, Failures: 3, Errors: 0, Skipped: 0, Time elapsed: 0.695 sec <<< FAILURE!
userSuppliedJaxbContext(retrofit2.converter.jaxb.JaxbConverterFactoryTest)  Time elapsed: 0.052 sec  <<< FAILURE!
org.junit.ComparisonFailure: expected:<...<?xml version="1.0" [encoding="utf-8"?> <feed xmlns="http://www.w3.org/2005/Atom" xmlns:idx="urn:atom-extension:indexing"> <id>tag:google.com,2005:reader/user/abc/state/com.google/alerts/abc</id> </feed> ]"> but was:<...<?xml version="1.0" [?><feed><id>tag:google.com,2005:reader/user/abc/state/com.google/alerts/abc</id></feed>]">
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at retrofit2.converter.jaxb.JaxbConverterFactoryTest.userSuppliedJaxbContext(JaxbConverterFactoryTest.java:107)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.junit.rules.ExternalResource$1.evaluate(ExternalResource.java:48)
    at org.junit.rules.RunRules.evaluate(RunRules.java:20)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:252)
    at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:141)
    at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:112)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.apache.maven.surefire.util.ReflectionUtils.invokeMethodWithArray(ReflectionUtils.java:189)
    at org.apache.maven.surefire.booter.ProviderFactory$ProviderProxy.invoke(ProviderFactory.java:165)
    at org.apache.maven.surefire.booter.ProviderFactory.invokeProvider(ProviderFactory.java:85)
    at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:115)
    at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:75)

xmlResponseBody(retrofit2.converter.jaxb.JaxbConverterFactoryTest)  Time elapsed: 0.019 sec  <<< FAILURE!
org.junit.ComparisonFailure: expected:<...converter.jaxb.Feed@[bc1e4518]> but was:<...converter.jaxb.Feed@[1f]>
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at retrofit2.converter.jaxb.JaxbConverterFactoryTest.xmlResponseBody(JaxbConverterFactoryTest.java:88)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.junit.rules.ExternalResource$1.evaluate(ExternalResource.java:48)
    at org.junit.rules.RunRules.evaluate(RunRules.java:20)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:252)
    at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:141)
    at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:112)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.apache.maven.surefire.util.ReflectionUtils.invokeMethodWithArray(ReflectionUtils.java:189)
    at org.apache.maven.surefire.booter.ProviderFactory$ProviderProxy.invoke(ProviderFactory.java:165)
    at org.apache.maven.surefire.booter.ProviderFactory.invokeProvider(ProviderFactory.java:85)
    at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:115)
    at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:75)

xmlRequestBody(retrofit2.converter.jaxb.JaxbConverterFactoryTest)  Time elapsed: 0.012 sec  <<< FAILURE!
org.junit.ComparisonFailure: expected:<...<?xml version="1.0" [encoding="utf-8"?> <feed xmlns="http://www.w3.org/2005/Atom" xmlns:idx="urn:atom-extension:indexing"> <id>tag:google.com,2005:reader/user/abc/state/com.google/alerts/abc</id> </feed> ]"> but was:<...<?xml version="1.0" [?><feed><id>tag:google.com,2005:reader/user/abc/state/com.google/alerts/abc</id></feed>]">
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at retrofit2.converter.jaxb.JaxbConverterFactoryTest.xmlRequestBody(JaxbConverterFactoryTest.java:79)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.junit.rules.ExternalResource$1.evaluate(ExternalResource.java:48)
    at org.junit.rules.RunRules.evaluate(RunRules.java:20)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:252)
    at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:141)
    at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:112)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.apache.maven.surefire.util.ReflectionUtils.invokeMethodWithArray(ReflectionUtils.java:189)
    at org.apache.maven.surefire.booter.ProviderFactory$ProviderProxy.invoke(ProviderFactory.java:165)
    at org.apache.maven.surefire.booter.ProviderFactory.invokeProvider(ProviderFactory.java:85)
    at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:115)
    at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:75)

What I can see is that the tags' attributes are missing.

Are you saying that this is a bug in Retrofit's converter? Does it work when using JAXB directly with Strings without Retrofit?

@JakeWharton I don't know whether it's a bug or not. Maybe I'm just stupid.

I now did:

Feed f = JAXB.unmarshal(new StringReader(SAMPLE_CONTACT_XML), Feed.class);
System.out.println("XXX");
System.out.println(f.id);
System.out.println("XXX");
assertThat(f).isEqualTo(SAMPLE_CONTACT);

and it fails, f.id is empty:

Running retrofit2.converter.jaxb.JaxbConverterFactoryTest
XXX

XXX

After removing xmlns="http://www.w3.org/2005/Atom" xmlns:idx="urn:atom-extension:indexing" from the feed tag it works.
Seems to be a namespace issue.

Thought I'd extend the JAXBConverterFactory but JaxbRequestConverter can't be extended :/
And JaxbConverterFactory is final 馃槂 馃敨

Ain't seem to be an issue with retrofit but with JAXB.

Couldn't manage to get it working with (empty) namespace so I'll filter the namespace from the responses:

OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder();
clientBuilder.
        addInterceptor(new Interceptor() {
            @Override
            public Response intercept(Chain chain) throws IOException {
                Request request = chain.request();
                Response response = chain.proceed(request);
                ResponseBody body = response.body();
                // filter response here
                ResponseBody newBody = ResponseBody.create(MediaType.parse("application/xml"), "<?xml version=\"1.0\" encoding=\"utf-8\"?> <feed> <id>xyz</id> </feed>");
                response = (new Response.Builder())
                    .body(newBody)
                    .code(response.code())
                    .headers(response.headers())
                    .message(response.message())
                    .protocol(response.protocol())
                    .receivedResponseAtMillis(response.receivedResponseAtMillis())
                    .request(response.request())
                    .build();
                return response;
            }
        });
Was this page helpful?
0 / 5 - 0 ratings