Usually with JAXB we are mapping POJOs to XML. In this example I will demonstrate how to handle a Java model that is exposed through a set of interfaces. This post was inspired by an answer I gave (feel free to upvote) to a question on Stack Overflow. In the question the poster was running into exceptions like the one below. This post will cover how to properly map this use case.
Demo Code
When dealing with interface fronted model classes, you interact with the model via the interfaces but you must be sure to bootstrap the JAXBContext on the implementation classes:
Interface Model
Exception in thread "main" com.sun.xml.bind.v2.runtime.IllegalAnnotationsException: 6 counts of IllegalAnnotationExceptions blog.interfaces.Customer is an interface, and JAXB can't handle interfaces. this problem is related to the following location: at blog.interfaces.Customer blog.interfaces.Customer does not have a no-arg default constructor. this problem is related to the following location: at blog.interfaces.Customer ...
Demo Code
When dealing with interface fronted model classes, you interact with the model via the interfaces but you must be sure to bootstrap the JAXBContext on the implementation classes:
package blog.interfaces; import java.io.File; import javax.xml.bind.JAXBContext; import javax.xml.bind.Marshaller; import javax.xml.bind.Unmarshaller; public class Demo { public static void main(String[] args) throws Exception { JAXBContext jc = JAXBContext.newInstance(CustomerImpl.class); Unmarshaller unmarshaller = jc.createUnmarshaller(); File xml = new File("src/blog/interfaces/input.xml"); Customer customer = (Customer) unmarshaller.unmarshal(xml); Address address = customer.getAddress(); System.out.println(address.getStreet()); for(PhoneNumber phoneNumber : customer.getPhoneNumbers()) { System.out.println(phoneNumber.getValue()); } Marshaller marshaller = jc.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); marshaller.marshal(customer, System.out); } }
Interface Model
In this example our domain model is exposed through a set of interfaces. These interfaces must not be included in the set of classes passed in to bootstrap the JAXBContext. If they are you will see an exception like the one mentioned earlier in this post.
Customer
package blog.interfaces; import java.util.List; public interface Customer { Address getAddress(); void setAddress(Address address); List<PhoneNumber> getPhoneNumbers(); void setPhoneNumbers(List<PhoneNumber> phoneNumbers); }
Address
package blog.interfaces; public interface Address { public String getStreet(); public void setStreet(String street); }
PhoneNumber
package blog.interfaces; public interface PhoneNumber { String getValue(); void setValue(String value); }
Implementation Classes
CustomerImpl
For fields/properties in which the type is an interface we will use the @XmlElement annotation to specify the concrete type. Note that for collection fields/properties the type specified in @XmlElement represents the item type, and not the collection type.
package blog.interfaces; import java.util.List; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement(name="customer") public class CustomerImpl implements Customer { private Address address; private ListphoneNumbers; @XmlElement(type=AddressImpl.class) public Address getAddress() { return address; } public void setAddress(Address address) { this.address = address; } @XmlElement(type=PhoneNumberImpl.class, name="phone-number") public List<PhoneNumber> getPhoneNumbers() { return phoneNumbers; } public void setPhoneNumbers(List<PhoneNumber> phoneNumbers) { this.phoneNumbers = phoneNumbers; } }
AddressImpl
package blog.interfaces; public class AddressImpl implements Address { private String street; public String getStreet() { return street; } public void setStreet(String street) { this.street = street; } }
PhoneNumberImpl
package blog.interfaces; import javax.xml.bind.annotation.XmlValue; public class PhoneNumberImpl implements PhoneNumber { private String value; @XmlValue public String getValue() { return value; } public void setValue(String value) { this.value = value; } }
XML (input.xml)
The following XML document can be used with this example:
<?xml version="1.0" encoding="UTF-8"?> <customer> <address> <street>1 Any Street</street> </address> <phone-number>555-1111</phone-number> <phone-number>555-2222</phone-number> <phone-number>555-3333</phone-number> </customer>
Further Reading
If you enjoyed this post, then you may also be interested in:
Hi,
ReplyDeleteThis problem is really killing me.
For marshalling, I don't understand why JAXB needs to be told the concrete class - surely it can work this out using reflection at runtime.
Annotating with concrete class implementations has 2 problems:
1) if you have multiple implementations this won't work
2) the point of programming to interfaces is completely lost
I've tried looking into using a custom JAXBContext but either providing an enumeration of concrete implementations or the jaxb.index file both seem terrible ways of doing this.
I can understand the argument for unmarshalling, but this seems bonkers for marshalling.
Hi,
DeleteIf you are using EclipseLink JAXB (MOXy), I have an approach you can use to make this work. Check out the following example:
- MOXy JAXB - Map Interfaces to XML
In that example it is a set of interfaces that are mapped. If you perform an unmarshal you will get back proxy objects. However when you marshal you can pass MOXy any implementation of those interfaces that you want.
-Blaise
I agree. the all point of interface doesn't work
DeleteHi Shahar,
DeleteDid you try the approach I recommended in my comment on Jan 31?
-Blaise