Why doesn't JAXB generate setters for Lists

JavaJaxbGetter Setter

Java Problem Overview


When I generate JAXB classes from an XSD, the elements with maxOccurs="unbounded" gets a getter method generated for them, but no setter method, as follows:

/**
 * Gets the value of the element3 property.
 * 
 * <p>
 * This accessor method returns a reference to the live list,
 * not a snapshot. Therefore any modification you make to the
 * returned list will be present inside the JAXB object.
 * This is why there is not a <CODE>set</CODE> method for the element3 property.
 * 
 * <p>
 * For example, to add a new item, do as follows:
 * <pre>
 *    getElement3().add(newItem);
 * </pre>
 * 
 * 
 * <p>
 * Objects of the following type(s) are allowed in the list
 * {@link Type }
 * 
 * 
 */
public List<Type> getElement3() {
    if (element3 == null) {
        element3 = new ArrayList<Type>();
    }
    return this.element3;
}

The method comment makes it crystal clear on how can I use it, but my question is as follows:
Why doesn't JAXB just generate a setter, following the Java Beans rules? I know I can write the setter method myself, but is there any advantage to the approach suggested in the generated getter method?

This is my XSD:

<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://www.example.org/DoTransfer/" targetNamespace="http://www.example.org/DoTransfer/">

    <element name="CollectionTest" type="tns:CollectionTest"></element>
    
    <complexType name="CollectionTest">
    	<sequence>
    		<element name="element1" type="string" maxOccurs="1" minOccurs="1"></element>
    		<element name="element2" type="boolean" maxOccurs="1" minOccurs="1"></element>
    		<element name="element3" type="tns:type" maxOccurs="unbounded" minOccurs="1" nillable="true"></element>
    	</sequence>
    </complexType>


    <complexType name="type">
    	<sequence>
    		<element name="subelement1" type="string" maxOccurs="1" minOccurs="1"></element>
    		<element name="subelement2" type="string" maxOccurs="1" minOccurs="0"></element>
    	</sequence>
    </complexType>
</schema>

Java Solutions


Solution 1 - Java

Here is the justification from the JAXB specification - page 60.

> Design Note – There is no setter method for a List property. The > getter returns the List by reference. An item can be added to the > List returned by the getter method using an appropriate method > defined on java.util.List. Rationale for this design in JAXB 1.0 was > to enable the implementation to wrapper the list and be able to > perform checks as content was added or removed from the List.

So if the implementation of the List was overriding add/remove to perform validation, replacing that 'special' List with (for instance) an ArrayList would defeat these checks.

Solution 2 - Java

Link for : No setter for list

> The code in the getter method ensures that the List is > created. There is no corresponding setter which means that all > additions or deletions of list elements have to be made on the "live" > list.

As the quote says that there is no setter as when you use the getter method it insures that a new instance of the list is initialized if not present.

And after that when you have to add or remove anything you will have to use

getElement3().add(Type);

UPDATE : Difference in marshalling for null and empty list

Example where list is null

@XmlRootElement(name = "list-demo")
public class ListDemo {

	@XmlElementWrapper(name = "list")
	@XmlElement(name = "list-item")
	private List<String> list;
	
}

OUTPUT will be

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<list-demo/>

Example where list is empty

@XmlRootElement(name = "list-demo")
public class ListDemo {

    @XmlElementWrapper(name = "list")
    @XmlElement(name = "list-item")
    private List<String> list = new ArrayList<String>();
    	
}

OUTPUT will be:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<list-demo>
    <list/>
</list-demo>

Solution 3 - Java

Agree with Patrick's concern above. If I was coding to the generated java classes directly I'd be happy to oblige, but I'm using an introspective tool expects either a setter or a directly accessible member. Had success using a plugin to XJC from https://github.com/highsource/jaxb2-basics/wiki/JAXB2-Setters-Plugin and adding a -B-Xsetter argument to wsimport

Solution 4 - Java

One can write their own XJC plugin for their specific requirement.

In this example one is trying to add an id field of type long in every generated java file from xjc:

package com.ricston;

import java.io.IOException;

import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;

import com.sun.codemodel.JBlock;
import com.sun.codemodel.JCodeModel;
import com.sun.codemodel.JFieldVar;
import com.sun.codemodel.JMethod;
import com.sun.codemodel.JMod;
import com.sun.codemodel.JType;
import com.sun.codemodel.JVar;
import com.sun.tools.xjc.BadCommandLineException;
import com.sun.tools.xjc.Options;
import com.sun.tools.xjc.Plugin;
import com.sun.tools.xjc.outline.ClassOutline;
import com.sun.tools.xjc.outline.Outline;

public class XJCPlugin extends Plugin {

  public final static String ID = "id";
	public final static JType LONG_TYPE = new JCodeModel().LONG;
	public final static String ID_GETTER = "getId";
	public final static JType VOID_TYPE = new JCodeModel().VOID;
	public final static String ID_SETTER = "setId";

	@Override
	public String getOptionName() {
		return "Xexample-plugin";
	}

	@Override
	public int parseArgument(Options opt, String[] args, int i)
			throws BadCommandLineException, IOException {
		return 1;
	}

	@Override
	public String getUsage() {
		return "  -Xexample-plugin    :  xjc example plugin";
	}

	@Override
	public boolean run(Outline model, Options opt, ErrorHandler errorHandler)
			throws SAXException {

		for (ClassOutline classOutline : model.getClasses()) {
			JFieldVar globalId = classOutline.implClass.field(JMod.PRIVATE,
					LONG_TYPE, ID);

			JMethod idGetterMethod = classOutline.implClass.method(JMod.PUBLIC,
					LONG_TYPE, ID_GETTER);
			JBlock idGetterBlock = idGetterMethod.body();
			idGetterBlock._return(globalId);

			JMethod idSetterMethod = classOutline.implClass.method(JMod.PUBLIC,
					VOID_TYPE, ID_SETTER);
			JVar localId = idSetterMethod.param(LONG_TYPE, "_" + ID);
			JBlock idSetterBlock = idSetterMethod.body();
			idSetterBlock.assign(globalId, localId);
		}
		return true;
	}

}

Full example here.

Another example here:

https://blog.jooq.org/2018/02/19/how-to-implement-your-own-xjc-plugin-to-generate-tostring-equals-and-hashcode-methods/

Their are plugins available for generating hashCode, equals, setters-for-list at github too.

References:

Answer to a question I had asked.

Solution 5 - Java

In case anyone is here looking for a way to get rid of those annoying lazy initializers in XJC-generated code... In my Maven POM, this goes under <build><plugins>:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-antrun-plugin</artifactId>
    <version>1.8</version>
    <executions>
        <execution>
            <id>remove-jaxb-generated-lazy-initializers</id>
            <phase>process-sources</phase>
            <goals>
                <goal>run</goal>
            </goals>
            <configuration>
                <target if="${remove-jaxb-generated-lazy-initializers}">
                    <echo message="Running 'replaceregexp' target on generated sources..."/>
                    <echo message="This removes JAXB-generated lazy initializers from collection accessors."/>
                    <replaceregexp match="if \([\w_]+ == null\) \{\s+[\w_]+ = new ArrayList&lt;[\w_]+&gt;\(\);\s+\}\s+" replace="" flags="g">
                        <fileset dir="${project.build.directory}/generated-sources" includes="**/*.java"/>
                    </replaceregexp>
                </target>
            </configuration>
        </execution>
    </executions>
</plugin>

For setters, I'm also adding @lombok.Setter to certain classes using org.jvnet.jaxb2_commons:jaxb2-basics-annotate and a bindings file. Thus the classes end up being standard beans.

I would love to hear it if anyone knows of a less hacky way--e.g., a XJC plugin.

Solution 6 - Java

Need to add the dependency inside the plugin. For example as below,

<plugin>
                    <groupId>org.codehaus.mojo</groupId>
                    <artifactId>jaxb2-maven-plugin</artifactId>
                    <version>1.5</version>
                    <dependencies>
                    <dependency>
                        <groupId>org.andromda.thirdparty.jaxb2_commons</groupId>
                        <artifactId>collection-setter-injector</artifactId>
                        <version>1.0</version>
                    </dependency>
                    </dependencies>
                    <configuration>
                        <schemaDirectory>${project.basedir}/epc</schemaDirectory>
                        <arguments>-Xcollection-setter-injector</arguments>
                        <clearOutputDir>false</clearOutputDir>
                        <extension>true</extension>
                    </configuration>
                    <executions>
                        <execution>
                            <id>generate-java-from-xsd-1</id>
                            <phase>generate-sources</phase>
                            <goals>
                                <goal>xjc</goal>
                            </goals>
                            <configuration>
                                <packageName>${package name}</packageName>
                                <schemaFiles>example.xsd</schemaFiles>
                                <schemaDirectory>${project.basedir}/epc</schemaDirectory>
                                <bindingFiles>example_1.xjb</bindingFiles>
                                <bindingDirectory>${project.basedir}/generate</bindingDirectory>
                                <staleFile>${project.build.directory}/jaxb2/.xjc1StaleFlag</staleFile>
                            </configuration>
                        </execution>
              </plugin>

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionAhmad Y. SalehView Question on Stackoverflow
Solution 1 - JavajdesseyView Answer on Stackoverflow
Solution 2 - JavaNarendra PathaiView Answer on Stackoverflow
Solution 3 - JavaChris KlopfensteinView Answer on Stackoverflow
Solution 4 - JavaSiddharth TrikhaView Answer on Stackoverflow
Solution 5 - JavaLucas RossView Answer on Stackoverflow
Solution 6 - JavaPawan GunjkarView Answer on Stackoverflow