30

I'm starting learning JAXB, so my question can be very silly. Now I have classes and want generate XML Schema. Going after this instruction I get exception

IllegalAnnotationExceptions ... does not have a no-arg default constructor.

Yeah. My classes haven't default no-arg constructors. It's too easy. I have classes with package visible constructors / final methods and off course with arguments. What shall I do - create some specific momemto/builder classes or specify my constructors to JAXB (in what way?) ? Thanks.

Stan Kurilin
  • 15,614
  • 21
  • 81
  • 132

4 Answers4

42

JAXB can support this case using an XML Adapter. Consider you have the following object with no zero-arg constructor:

package blog.immutable;

public class Customer {

    private final String name;
    private final Address address;

    public Customer(String name, Address address) {
        this.name = name;
        this.address = address;
    }

    public String getName() {
        return name;
    }

    public Address getAddress() {
        return address;
    }

}

You simply need to create a mappable version of this class:

package blog.immutable.adpater;

import javax.xml.bind.annotation.XmlAttribute;
import blog.immutable.Address;

public class AdaptedCustomer {

    private String name;
    private Address address;

    @XmlAttribute
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

}

And an XML Adapter to convert between them:

package blog.immutable.adpater;

import javax.xml.bind.annotation.adapters.XmlAdapter;
import blog.immutable.Customer;

public class CustomerAdapter extends XmlAdapter<AdaptedCustomer, Customer> {

    @Override
    public Customer unmarshal(AdaptedCustomer adaptedCustomer) throws Exception {
        return new Customer(adaptedCustomer.getName(), adaptedCustomer.getAddress());
    }

    @Override
    public AdaptedCustomer marshal(Customer customer) throws Exception {
        AdaptedCustomer adaptedCustomer = new AdaptedCustomer();
        adaptedCustomer.setName(customer.getName());
        adaptedCustomer.setAddress(customer.getAddress());
        return adaptedCustomer;
    }

}

Then for properties that refer to the Customer class, simply use the @XmlJavaTypeAdapter annotation:

package blog.immutable;

import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import blog.immutable.adpater.CustomerAdapter;

@XmlRootElement(name="purchase-order")
public class PurchaseOrder {

    private Customer customer;

    @XmlJavaTypeAdapter(CustomerAdapter.class)
    public Customer getCustomer() {
        return customer;
    }

    public void setCustomer(Customer customer) {
        this.customer = customer;
    }

} 

For a more detailed example see:

bdoughan
  • 147,609
  • 23
  • 300
  • 400
  • 3
    please don't copy links directly, explain the basics in your answer, and eventually link to the blog post if more explanation is needed. This way, if the link drop dead, we'll still have the answer. – Valentin Rocher Dec 08 '10 at 12:45
  • 2
    I agree with not just copying links. This answer was posted during my commute to work from my phone. I'll provide more details when I'm at a computer. The link is to my own blog if that helps – bdoughan Dec 08 '10 at 13:18
  • 1
    As promised now that I'm back at a computer I have updated my answer with a complete example demonstrating the solution. Perhaps the down voter will consider reversing their vote? – bdoughan Dec 08 '10 at 14:08
  • Yes, sorry, was my turn of not being at my computer ;) – Valentin Rocher Dec 08 '10 at 14:14
  • Wow, that's really 'simple'! I suggest using this trick instead: http://stackoverflow.com/a/6700837/4590852 – A. L. Mar 23 '17 at 13:24
16

You can use the annotation @XmlType and use factoryMethod / factoryClass attributes in various combinations such as:

@XmlType(factoryMethod="newInstance")
@XmlRootElement
public class PurchaseOrder {
    @XmlElement
    private final String address;
    @XmlElement
    private final Customer customer;

    public PurchaseOrder(String address, Customer customer){
        this.address = address;
        this.customer = customer;
    }

    private PurchaseOrder(){
        this.address = null;
        this.customer = null;
    }
    /** Creates a new instance, will only be used by Jaxb. */
    private static PurchaseOrder newInstance() {
        return new PurchaseOrder();
    }

    public String getAddress() {
        return address;
    }

    public Customer getCustomer() {
        return customer;
    }
}

Surprisingly this works and you get an initialized instance when unmarshalling. You should make note not to call the newInstance method anywhere on your code as it will return an invalid instance.

Rafael M
  • 381
  • 3
  • 12
  • 3
    Creating a public method will make it accessible to anyone, not just JAXB, potentially undermining your design and nullifying and constraints on fields introduced at design time. I cannot recommend this solution! – Eric Tobias Nov 17 '14 at 07:20
  • Yes, I'm aware of that but as always it depends on what are you willing to concede for sake of simplicity. You could play along defining interfaces for all your methods thus hiding any other utility method but then again I'm not sure what the original poster wanted to solve. – Rafael M Nov 24 '14 at 19:24
  • 3
    You can change the modifier to `protected` or `private`. So your design will not totally undermined. – thomas.mc.work Mar 18 '15 at 16:06
  • 1
    Can confirm that the use of private on newInstance() mitigates @Eric concern. – muttonUp Aug 09 '18 at 12:14
  • 1
    Updated response with private – Rafael M Aug 15 '18 at 18:21
  • Thank you for following up after all those years! – Eric Tobias Aug 22 '18 at 21:21
  • Actually, it looks like there's no need to add the newInstance() and @XmlType stuff. Simply setting the no-args constructor to be private still works fine with jaxb. – David Robertson Feb 28 '20 at 22:43
5

You should have a default constructor for JAXB to be able to instantiate your classes. Maybe there is a workaround I don't know though.

JAXB is especially fitted for bean-like classes, permitting to configure objects by calling setters on them.

Guillaume
  • 5,535
  • 1
  • 24
  • 30
  • 1
    Creating default constructor for all classes is a very bad think. There are must be some workaround. – Stan Kurilin Dec 08 '10 at 12:29
  • I frankly don't really understand the no-args racism. Your bean classes should only be containers for info, which can easily be transformed into XML, not classes with complex initialisation. – Valentin Rocher Dec 08 '10 at 12:32
  • 1
    This relates to a preference toward immutable data objects. – Guillaume Dec 08 '10 at 12:34
  • @Valentin Rocher, there isn't complex very initialization, but there are Objects with immutable state. – Stan Kurilin Dec 08 '10 at 12:36
  • 2
    If JAXB can create your object, it's not immutable. JAXB can't guess which constructor to use, because the possible other operations in the constructor could lead to faulty objects. – Valentin Rocher Dec 08 '10 at 12:39
  • Check out my answer to see how this can be done with JAXB: http://stackoverflow.com/questions/4387296/jaxb-and-constructors/4387534#4387534 – bdoughan Dec 08 '10 at 12:44
3

JAXB re-creates beans from XML in a simple fashion : it creates a new instance of the bean, and then do all the setXXX needed to set the attributes. So, if your bean doesn't have a no-args constructor, JAXB can't create it. As said in other answers, JAXB works better for simple "container" beans, for which no-args constructor isn't really a problem. If you're trying to create beans that need specific initialization, you'll need to do it in the setXXX methods.

Valentin Rocher
  • 11,667
  • 45
  • 59
  • 2
    I think there are must be some practice for working with immutable objects and so on.. – Stan Kurilin Dec 08 '10 at 12:40
  • @Stas : see my comment on Guillaume's answer. If JAXB has to create it, it can't be immutable. JAXB is not made for creating immutable objects, but more "transport" objects that you take info from later on. – Valentin Rocher Dec 08 '10 at 12:41
  • Or to put it simpler, if it doesn't have a public no-args constructor, it's not a bean. That's part of the javabean spec. – Joeri Hendrickx Dec 08 '10 at 14:03
  • This can be done using an XML Adapter, see my updated answer for more details: http://stackoverflow.com/questions/4387296/jaxb-and-constructors/4387534#4387534 – bdoughan Dec 08 '10 at 14:09
  • What I would generally do is use a factory pattern. Make a no argument constructor for JAXB. Then make a static factory method that takes all the arguments and handles calling the setters. – Jon May 31 '17 at 19:27