Skip to main content

Using JAXB 2.0's XmlJavaTypeAdapter

Posted by kohsuke on September 30, 2005 at 3:51 PM PDT

Sometimes when you are binding your own classes to XML, you hit with a situation where your class representation doesn't quite match what you'd like to see in the XML. Some other times, some of your classes hit the limitation in JAXB that the class must have a default constructor. XmlJavaTypeAdapter is a solution for those problems.

Consider the following question Phil asked in the JAXB&JAX-WS forum:

How do I work around this?
-phil

error: java.util.Currency does not have a no-arg default constructor
this problem is related to the following location:
at java.util.Currency
at com.tickets.common.util.Money.getCurrency (Unknown Source)
at com.tickets.common.util.Money (Unknown Source)

The problem happens when you have a code like this:

package com.tickets.common.util;

import java.util.Currency;

class Money {
   Currency getCurrency() { ... }
}

Except a few classes that JAXB implementations would recognize out of the box, classes need to have a default no-argument constructor (because otherwise JAXB wouldn't know how to instanciate it.) java.util.Currency doesn't have one, hence the complaint.

To solve this problem, first, you write an 'adapter' class, that extends XmlAdapter. Your code will be converting Currency to something else that JAXB knows how to handle. I think you'd probably want currency to show up in XML as a string, so your adapter will be converting Currency and String. The adapter class would look like this:

public class CurrencyAdapter extends XmlAdapter<String,Currency> {
    public Currency unmarshal(String val) throws Exception {
        return Currency.getInstance(val);
    }
    public String marshal(Currency val) throws Exception {
        return val.toString();
    }
}

You can then put @XmlJavaTypeAdapter on a place where your code references Currency like this:

class Money {
  @XmlJavaTypeAdapter(CurrencyAdapter.class)
  Currency getCurrency() {...}
}

If you have a lot of properties it becomes tedious to annotate each currency property in this way. If so, you can place this annotation as a package. This you do in package-info.java like this:

@XmlJavaTypeAdapter(value=CurrencyAdapter.class,type=Currency.class)
package com.tickets.common.util;

This tells JAXB that every reference to Currency inside your util package needs to be adapted by the specified adapter.

Finally, if the Currency class is your own class (which is not in this case, but for others who may have a similar situation), you can place this annotation on the class directly:

@XmlJavaTypeAdapter(CurrencyAdapter.class)
class Currency {
  ...
}

... to indicate that all references to this class be using the adapter. That's the basics of using XmlAdapter. Hope this explanation helps you get going, Phil!

Related Topics >>

Comments

Clarification: The package-info.java goes into the package ...

Clarification: The package-info.java goes into the package of the class where you actually need the annotation, not where the interface class or its adapter are defined.

So if you have e.g. a WebService:

package com.acme.service;
@WebService
public class MyService {
  public setMyList(List<IDatapoint> myList) {...}

the package-info.java is stored in package com.acme.service. It may contain something like:

@XmlJavaTypeAdapters({ 
@XmlJavaTypeAdapter(value = Datapoint.Adapter.class, type = IDatapoint.class),
@XmlJavaTypeAdapter(value = Alarm.Adapter.class, type = IAlarm.class)
})
package com.acme.service;

import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapters;

import com.acme.service.model.Datapoint;
import com.acme.service.model.IDatapoint;
import com.acme.service.model.Alarm;
import com.acme.service.model.IAlarm;

This solution solves the problem of where to put the @XmlJavaTypeAdapter annotation if you don't want/can't annotate the interface directly, and the methods parameter can't be annotated directly because it is a List.

How can I write adapter for a field which is generated in ...

How can I write adapter for a field which is generated in following way:
protected List xxx;

during unmarshalling the list is filled with Strings and LinkedHashMap instances which jaxb cannot then marshall to xml. I have already added to package-info.java following line:

@javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter(value=pl.adapters.MyListAdapter.class, type=java.util.List.class)

my adapter:
public final class MyListAdapter extends XmlAdapter<List, List> {

@Override
public List unmarshal(List feedList) {
List targetList = new ArrayList();
for (Object val : feedList)
targetList.addAll(feedList);

return targetList;
}

@Override
public List marshal(List feedList) {
List targetList = new ArrayList();
targetList.addAll(feedList);

return targetList;
}
}

As a result I got bunch of exceptions like this:

@XmlAttribute/@XmlValue need to reference a Java type that maps to text in XML.
this problem is related to the following location:
at protected java.util.List pl.generated.ExtendedTextInteraction.clazzs
at pl.generated.ExtendedTextInteraction
at protected java.util.List pl.generated.Div.content
at pl.generated.Div
at protected java.util.List pl.generated.TemplateBlock.content
at pl.generated.TemplateBlock
at protected java.util.List pl.generated.InfoControl.content
at pl.generated.InfoControl
at protected java.util.List pl.generated.FeedbackBlock.content
at pl.generated.FeedbackBlock
at protected java.util.List pl.generated.GapMatchInteraction.feedbackBlocksAndTemplateBlocksAndMaths
at pl.generated.GapMatchInteraction
at protected java.util.List pl.generated.Dd.content
at pl.generated.Dd
at protected java.util.List pl.generated.Dl.ddsAndDts
at pl.generated.Dl
at protected java.util.List pl.generated.Prompt.content
at pl.generated.Prompt
at protected pl.generated.Prompt pl.generated.UploadInteraction.prompt
at pl.generated.UploadInteraction
at protected java.util.List pl.generated.Blockquote.positionObjectStagesAndCustomInteractionsAndDrawingInteractions
at pl.generated.Blockquote
at protected java.util.List pl.generated.Object.content
at pl.generated.Object
at public pl.generated.Object pl.generated.ObjectFactory.createObject()
at pl.generated.ObjectFactory

EDIT: my problem is exactly the same as here:
http://stackoverflow.com/questions/15252578/jaxb-xmladapter-for-listobje...

I know it's a little late but

I know it's a little late but it would be great to be able to specify adapters at package level in xjc:

[jxb:bindings schemaLocation="..." node="/xs:schema"]
- [jxb:schemaBindings]
-- [jxb:package name="..."]
--- [xjc:javaType name="..." adapter="..." /]

but currently, jxp:package doesn't allow child elements other than javadoc.

What should I do if Money is

What should I do if Money is a third party class and I cann't annotate it?

is this supposed to work with choice groups?

I have a field annotated as:
@XmlElements({
@XmlElement(name="int",type=B1.class),
@XmlElement(name="string",type=B2.class),
@XmlElement(name="method",type=B3.class),
...
})
private A chain;

with a class-level adapter annotation on each Bx class:
@XmlJavaTypeAdapter(B1Adapter.class)
class B1 extends A

however, i still get the "B1 doesn't have a default no-arg constructor" error... why does this not work?